【以太网模块】W5500模块使用 - 看这篇就够了【基本都有 - 中断/非中断工作方式/寄存器使用/官方库移植使用】_w5500以太网模块
W5500模块原理及开发:
W5500/以太网传感器(基于W5500官方驱动库+HAL库) · 语雀资料:参考:W5500以太网模块官方驱动移植_w550...https://www.yuque.com/u41716106/fgbb99/tu62o3atlc2falan?singleDoc#
整理,研究了很久,基本的配置都摸索清楚了,特此分享,有问题希望能够指出
参考了很多文章,但是感觉很多都写的或多或少的,得去看数据手册或官方源码研究
资料:
参考:
- W5500以太网模块官方驱动移植_w5500驱动-CSDN博客
- W5500简单使用及官方IO库 快速入门_w5500 io-p实例-CSDN博客
- 如何移植使用W5500官方提供的最新Socket库_w5500官方库-CSDN博客
- STM32使用HAL库驱动W5500-CSDN博客
- STM32配置W5500_w5500配置流程-CSDN博客
- 【STM32+FreeRTOS +W5500移植要点】30,RTOS中断;从TIM2,主TIM3;RTOS主要用在LCD中;RT-Thread;标志重定义问题 2019年01月22日_stm32 freertos w5500-CSDN博客
- 【STM32CubeMX】STM32H7-RTOS-SPI-W5500_stm32cubemx 配置stm32h7-CSDN博客
- STM32+W5500+以太网应用开发+001_Ping 新建工程,移植驱动_stm32 w5500移植-CSDN博客
- (二)原创调试W5500芯片--W5500的初始化过程 - Tony.Jia - 博客园
- STM32+W5500+以太网应用开发+002_TCP 服务器和客户端_w5500 tcp客户端-CSDN博客
驱动库函数API详解:
- Ethernet | Mbed
资料:
- GitHub - PeterBorisenko/w5500-lwip-freertos: RTOS-based binding for W5500 and LwIp
- W5500 TCP/IP芯片 官方库文件阅读_w5500官方库-CSDN博客
- W5500官方例程:https://www.w5500.com/code.html
- W5500系列官网驱动:GitCode - 全球开发者的开源社区,开源代码托管平台
📎ioLibrary_Driver-master.zip
- W5500芯片数据手册:📎W5500中文数据手册.pdf【看宏定义/枚举值/寄存器】
- 网络调试助手:📎网络调试助手.rar
模块信息:
WIZ:以太网模块
CHIP:芯片
WHZCHIP:以太网芯片
PHY:以太网
W5500以太网模块介绍:
D-W5500 EVB以太网模块是一款基于WIZnet W5500芯片的以太网模块,且性价比高的以太网模块。W5500是一款全硬件TCP/IP嵌入式以太网控制器,为嵌入式系统提供了更加建议的互联网连接方案。
W5500固化了TCP/IP协议栈,10/100Mbps以太网数据链路层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。
内嵌32K字节片上缓存以供以太网处理,并且可以同时使用8个硬件Socket独立通讯;(有8个Socket,一个就可以开一个TCP客户端/服务器了)
SPI(外设船型接口)从而能够更加容易与外设MCU整合,并且W5500使用了高效SPI协议支持80MHz,从而实现高速网络通讯。
模块还支持3.3V或者5V电源供电,当5V供电时还可以输出3.3V的电压,方便用户在不同的单片机系统中使用。
引脚分析及初始化配置:
STM32+W5500+以太网应用开发+001_Ping 新建工程,移植驱动_stm32 w5500移植-CSDN博客
pin
功能
3.3V/5V
电源输入
GND
电源地
INT
中断引脚
RST
硬件复位引脚
MISO
SPI主机输入从机输出引脚
MOSI
SPI主机输出从机输入引脚
SCS/CS
SPI SLAVE选择引脚
SCLK/SCK
SPI时钟引脚
- 电源的输入和地:
正常连接,注意和主芯片共地,不然SPI通讯可能会出问题。
- 下面四个就是SPI通讯用到的线路:SPI配置(三个)+GPIO输出模式(SCS,一个)
据自己的芯片引脚连接好。
- INT是W5500用于输出中断信号:外部中断,上拉输入,下降沿检测
W5500输出中断时会拉低这个引脚,另外,如果同时有多个中断的话,W5500的机制是会一个个的输出中断,比如同时有两个中断的话,会先拉低一小会,然后拉高,然后一会后再拉低。 如果要用中断机制的话,把这个线接到MCU有下拉中断的引脚上,然后还需要对W5500内部寄存器进行一定的设置,如果不用的话可以忽略这个引脚。
- RST是W5500的硬件复位引脚:普通IO即可,输出
通过拉低其一会然后再拉高可以强制W5500复位。另外还有软复位,所以其实可以不用这个引脚,不用的话就直接一直拉高就好了。根据需要进行连接。
移植:(仅到完成注册spi函数部分,不包括芯片初始化编程)
基于官方驱动的移植分析
主要参考:W5500简单使用及官方IO库 快速入门_w5500 io-p实例-CSDN博客
1. 官方库包分析
Wiznet ioLibrary_Driver 开源项目指南-CSDN博客
整个官方库的包的文件结构如下:
application/loopback
:是一个回环测试的示例程序。Internet
:里则是上层多种协议的驱动程序。
库中最重要的就是Ethernet
里头的几个文件:wizchip_conf.h/c
:有用户用于注册函数的几个api以及配置W5X00的一些驱动函数,比如close connect socket等等函数,实现了一些应用层的封装。【接口】
wizchip_conf.c 就是一个公共文件主要包括和SPI的连接以及连接W5500读写寄存器函数和socket函数。有用户用于注册函数的几个api以及配置W5X00的一些驱动函数。W5X00.h/c
:则是几个基础IO函数以及芯片上寄存器的相关定义和读写函数,主要实现的是对W5500模块寄存器的读写和操作,包括读写那些寄存器,地址是多少等等,。socket.h/c
:则提供了类berkeley socket api的接口函数,直接用于驱动TCP/IP功能。其中大量调用另外两个文件中提供的驱动函数,可以将其视作更高层的抽象。当然,抽象是要付出代价的,如果希望省点代码的话,可以直接调用真正的底层函数。
2. 在wizchip_conf.h
中选择开发的以太网模块型号
这个库其实是兼容W5100、W5200、W5300、W5500。
使用之前,在wizchip_conf.h把以下宏设置为5500(自己的型号)
3. 移植分析
3.1. 实现与W5500通信的SPI驱动
3.2. 当前MCU的SPI驱动注册到W5500的驱动中——临界区函数注册
为了兼容性,库无法假设SPI和临界区的具体实现,于是需要用户主动把相关驱动函数注册给库使用。
// 注册进入\\离开临界区函数// cris_en 进入// cris_ex 离开void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void));// 注册spi片选函数// cs_sel 选定// cs_desel 取消选定void reg_wizchip_cs_cbfunc(void(*cs_sel)(void), void(*cs_desel)(void));// 注册spi读写单字节函数// spi_rb 从spi读一个字节// spi_wb 往spi写一个字节void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb));// 注册spi大量读写函数// spi_rb 从spi读取len个字节到pBuf// spi_wb 从pBuf往spi写len个字节void reg_wizchip_spiburst_cbfunc(void (*spi_rb)(uint8_t* pBuf, uint16_t len), void (*spi_wb)(uint8_t* pBuf, uint16_t len));
注:这些函数都有默认的空函数,所以不需要用到某个功能的时候直接不注册就行。
注:其实还有一个注册总线接口的,但是由于W5500只有SPI接口,所以这个函数就直接忽略吧。
关于临界区函数的注册问题:有多线程就要注册
临界段:
代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。
为确保临界段代码的执行不被中断,在进入临界段之前须关中断(进入临界函数),而临界段代码执行完毕后,要立即开中断(离开临界函数)。
临界区函数会在内部基础IO函数的开始和结束时被调用,它保证单次读写W5500寄存器的原子性,当然,实际上一个简单的功能可能都要读写好几个寄存器,如果你需要保证更大范围的互斥操作,那还得自己实现些其他机制。
如果是单线程实现所有与W5500的通讯的话,那其实注不注册这个函数也无所谓。
但如果使用了多线程来与W5500通信就要注意这个函数。【使用了操作系统】
这个临界函数要实现:全局中断/任务的关闭与开启
【暂时测试一下只关闭STM32端的全部中断,没有使用RTOS的API来解决任务的,任务中暂时也未产生冲突】
STM32关全局中断开全局中断多种方式_stm32全局中断-CSDN博客
【STM32CubeMX】STM32H7-RTOS-SPI-W5500_stm32cubemx 配置stm32h7-CSDN博客
FreeRTOS 临界段和开关中断 - Crystal_Guang - 博客园
//进入void SPI_Cris_Enter(void) 或者 w5500_enter_critical(void){ __set_PRIMASK(1); /* 禁止全局中断*/或者 __disable_irq();/* 禁止全局中断*/} void SPI_Cris_Exit(void) 或者 w5500_exit_critical(void){ __set_PRIMASK(0) /* 使能全局中断 */或者 __enable_irq(); /* 使能全局中断 */}
片选函数:一定要注册
片选函数这是在进入临界区之后立刻被使用的函数,它用于通知W5500通讯的开始和结束。所以一定要正确注册这两个函数,在选定时拉低片选信号,取消选定时拉高。
读写函数:至少注册单字节读写
SPI读写函数则就没什么好说的了,基础IO嘛,要不人家库怎么知道去哪里收发数据。
注册示例(具体根据自己实际情况):
#include \"SPI.h\"……uint8_t spi_readbyte(void) {return SPI_ExchangeChar(SPI0,0xaa);}void spi_writebyte(uint8_t wb) {SPI_ExchangeChar(SPI0,wb);}void spi_readburst(uint8_t* pBuf, uint16_t len) { for(;len > 0;len--, pBuf++){ *pBuf = SPI_ExchangeChar(SPI0,0xaa); }}void spi_writeburst(uint8_t* pBuf, uint16_t len) { for(;len > 0;len--, pBuf++){ SPI_ExchangeChar(SPI0,*pBuf); }}void cs_select(void) {PTS_PTS7 = 0;}void cs_deselect(void) {PTS_PTS7 = 1;} int main(){ // 设置S7为输出引脚(用于片选) DDRS_DDRS7 = 1; // 初始化SPI0 SPI_Init(SPI0); SPI_Enable(SPI0); // 注册驱动函数 //这里没有用到临界区函数,可以不注册:不调用注册函数/调用给NULL reg_wizchip_cris_cbfunc(NULL, NULL); reg_wizchip_spi_cbfunc(spi_readbyte,spi_writebyte); reg_wizchip_spiburst_cbfunc(spi_readburst,spi_writeburst); reg_wizchip_cs_cbfunc(cs_select,cs_deselect); //reg_wizchip_cris_cbfunc(cris_en, cris_ex); ……}
/** * @brief 写1字节数据到SPI总线 * @param TxData 写到总线的数据 * @retval None */void SPI_WriteByte(uint8_t TxData){ HAL_SPI_Transmit(&SPI_HANDLE, &TxData, 1, 0xFF); }/** * @brief 从SPI总线读取1字节数据 * @retval 读到的数据 */uint8_t SPI_ReadByte(void){ uint8_t send = 0xff; uint8_t recv; HAL_SPI_TransmitReceive(&SPI_HANDLE, &send, &recv, 1, 0xff); return recv; }/** * @brief 进入临界区 * @retval None */void SPI_CrisEnter(void){ __set_PRIMASK(1);}/** * @brief 退出临界区 * @retval None */void SPI_CrisExit(void){ __set_PRIMASK(0);}/** * @brief 片选信号输出低电平 * @retval None */void SPI_CS_Select(void){ CSPin(0); //低电平有效}/** * @brief 片选信号输出高电平 * @retval None */void SPI_CS_Deselect(void){ CSPin(1);}
uint8_t SPIReadWrite(uint8_t TxData){ uint8_t RxData; HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)&TxData, &RxData, 1, 100); return RxData;}void wizchip_select(void){HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_RESET);}void wizchip_deselect(void){HAL_GPIO_WritePin(W5500_CS_GPIO_Port, W5500_CS_Pin, GPIO_PIN_SET);}uint8_t wizchip_read(void){uint8_t rb;rb=SPIReadWrite(0x00);return rb;}void wizchip_write(uint8_t wb){SPIReadWrite(wb);}void wizchip_readburst(uint8_t* pBuf, uint16_t len){for(uint16_t i=0;i<len;i++){*pBuf=SPIReadWrite(0x00);pBuf++;}}void wizchip_writeburst(uint8_t* pBuf, uint16_t len){for(uint16_t i=0;i<len;i++){SPIReadWrite(*pBuf);pBuf++;}}void W5500IOInit(void){ //使用STM32Cube生成的IO引脚初始化}void w5500_enter_critical(void){__disable_irq();}void w5500_exit_critical(void){__enable_irq();}
4. 补充以及初始化时遇到的问题:
4.1. stdint.h文件
如果编译时报错说include不到stdint.h,可以自己加入这个头文件。
#ifndef _STDINT_H#define _STDINT_Htypedef signed char int8_t;typedef unsigned char uint8_t;typedef signed short int16_t;typedef unsigned short uint16_t;typedef signed long int32_t;typedef unsigned long uint32_t;
4.2. 关于注册流程
4.3. 电脑的某个网络适配器ip变为169.264.xxx,此时是无法使用的该网络
如果为自动配置ipv4,那就手动给静态ip
如果已经是静态ip,那说明该ip与局域网内的重复了
4.4. 网络参数打印乱码
后面排查出来是串口的问题,多变参数传入有问题
(通用)基本流程
参考:
W5500以太网模块官方驱动移植_w5500驱动-CSDN博客
如何移植使用W5500官方提供的最新Socket库_w5500官方库-CSDN博客
STM32使用HAL库驱动W5500-CSDN博客
...看顶部
1. 下载W5500官网驱动
GitCode - 全球开发者的开源社区,开源代码托管平台
ioLibrary 驱动程序:
ioLibrary 代表 WIZnet 芯片的“互联网卸载库”,它包含驱动程序和应用协议。
该驱动程序(ioLibrary)可用于基于 WIZnet TCP/IP 芯片的应用设计,如 W5500,W5300,W5200,W5100 和 W5100S。
📎ioLibrary_Driver-master.zip
2. 根据芯片型号,在下载的驱动库中,分别移植以下几个文件到工程中:
3. 开始移植:配置为当前以太网模块型号,实现SPI通信功能
3.1. 配置芯片型号(与主机接口模式)
- 在
wizchip_conf.h
中定义使用的芯片 - 在
wizchip_conf.h
中定义使用的主机接口模式
3.2. 实现MCU与W5500的SPI驱动,然后将当前MCU的SPI驱动注册到W5500驱动中:
根据不同的芯片实现不同的流程(参考下面)
要实现以下这些函数(利用当前芯片的SPI功能):
1、注册进入\\离开临界区函数// cris_en 进入// cris_ex 离开void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void));2、注册spi片选函数// cs_sel 选定// cs_desel 取消选定void reg_wizchip_cs_cbfunc(void(*cs_sel)(void), void(*cs_desel)(void));3、注册spi读写单字节函数// spi_rb 从spi读一个字节// spi_wb 往spi写一个字节void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb));4、注册spi大量读写函数// spi_rb 从spi读取len个字节到pBuf// spi_wb 从pBuf往spi写len个字节void reg_wizchip_spiburst_cbfunc(void (*spi_rb)(uint8_t* pBuf, uint16_t len), void (*spi_wb)(uint8_t* pBuf, uint16_t len));
基于STM32F103+HAL库+FREERTOS 移植流程:
F103RCT6
1. 根据以太网芯片型号,在下载的驱动库中,分别移植以下几个文件到工程中:
2. 开始移植:配置为当前以太网模块型号,实现SPI通信功能
2.1. 配置芯片型号(与主机接口模式)
- 在
wizchip_conf.h
中定义使用的芯片 - 在
wizchip_conf.h
中定义使用的主机接口模式
2.2. 实现MCU与W5500的SPI驱动:
CubeMX配置W5500引脚:
参考“引脚分析及初始化配置”,里面有CubeMX配置,如果要看标准库,看例程
Project中实现注册的SPI相关函数:
在w5500.h.c中实现SPI相关函数,同时引脚宏定义也放进去
/* * @Author: eternal_fu * @Date: 2024-09-20 20:14:16 * @LastEditors: Please set LastEditors * @LastEditTime: 2024-09-20 22:00:20 * @Description: 基于当前MCU的SPI实现W5500的注册函数 */#include \"driver_w5500_spi.h\"/*********************************************************//***************----- W5500 引脚初始化 -----***************//*********************************************************//** * @description: W5500引脚初始化 * @return {*} */void W5500_IO_Init(void){//CubeMX生成: /* SPI(MOSI,MISO,SCLK)初始化 */ /* GPIO(CS,INT)初始化 */ /* NVIC初始化 */ /* 保存spi句柄 */ W5500_SPI_HANDLE = hspi1;}/****************************************************************************//***************----- W5500 实现MCU-SPI驱动(被注册的API) -----***************//****************************************************************************//******************************************************************************** 函数名 : SPI_Write_Byte* 描述 : 写1字节数据到SPI总线* 输入 : TxData 写到总线的数据* 输出 : 无* 返回值 : 无* 说明 : 注册到——void wizchip_spi_writebyte(uint8_t wb) {}*******************************************************************************/void SPI_Write_Byte(uint8_t TxData){ HAL_SPI_Transmit(&W5500_SPI_HANDLE, &TxData, 1, 0xFF); }/******************************************************************************** 函数名 : SPI_Read_Byte* 描述 : 从SPI总线读取1字节数据* 输入 : 无* 输出 : 无* 返回值 : 读到的数据* 说明 : 注册——uint8_t wizchip_spi_readbyte(void) {return 0;}*******************************************************************************/uint8_t SPI_Read_Byte(void){ uint8_t send = 0xff; uint8_t recv; HAL_SPI_TransmitReceive(&W5500_SPI_HANDLE, &send, &recv, 1, 0xff); return recv; }/******************************************************************************** 函数名 : SPI_WriteBurst_Byte* 描述 : spi大量写函数,从pBuf往spi写len个字节* 输入 : ...* 输出 : 无* 返回值 : 无* 说明 : 注册到——void wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len) {}*******************************************************************************/void SPI_WriteBurst_Byte(uint8_t* pBuf, uint16_t len) { for (; len > 0; len--, pBuf++) { SPI_Write_Byte(*pBuf); }}/******************************************************************************** 函数名 : SPI_ReadBurst_Byte* 描述 : spi大量读函数,从spi读取len个字节到pBuf* 输入 : ...* 输出 : 无* 返回值 : 读到的数据* 说明 : 注册——uint8_t wizchip_spi_readbyte(void) {return 0;}*******************************************************************************/void SPI_ReadBurst_Byte(uint8_t* pBuf, uint16_t len){ for (; len > 0; len--, pBuf++) { *pBuf = SPI_Read_Byte(); }}/******************************************************************************** 函数名 : SPI_Cris_Enter* 描述 : 进入临界区* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 关闭STM32全局中断,用了操作系统(多线程)需要注册*******************************************************************************/void SPI_Cris_Enter(void){ __set_PRIMASK(1); /* 禁止全局中断*/ //或者__disable_irq();}/******************************************************************************** 函数名 : SPI_Cris_Exit* 描述 : 退出临界区* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 关闭STM32全局中断,用了操作系统(多线程)需要注册*******************************************************************************/void SPI_Cris_Exit(void){ __set_PRIMASK(0); /* 使能全局中断 */ //或者__enable_irq(); }/******************************************************************************** 函数名 : SPI_CS_Select* 描述 : 选定,片选信号输出低电平* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 无*******************************************************************************/void SPI_CS_Select(void){ W5500_CS_0(); //低电平有效}/******************************************************************************** 函数名 : SPI_CS_Deselect* 描述 : 取消选定,片选信号输出高电平* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 无*******************************************************************************/void SPI_CS_Deselect(void){ W5500_CS_1();}
/* * @Author: eternal_fu * @Date: 2024-09-20 20:14:10 * @LastEditors: Please set LastEditors * @LastEditTime: 2024-09-20 21:34:41 * @Description: * 基于当前MCU-SPI编写的W5500的注册函数实现 * 具体可看#include \"wizchip_conf.h\" */#ifndef __driver_w5500__spi__#define __driver_w5500__spi__#include \"spi.h\"//mcu-spi1与w5500通信/***************----- W5500 SPI定义 -----***************/ extern SPI_HandleTypeDef hspi1;SPI_HandleTypeDef W5500_SPI_HANDLE; //在初始化的时候赋予W5500SPI句柄// SPI_HandleTypeDef W5500_SPI_HANDLE = hspi1;/***************----- W5500 GPIO定义 -----***************/ #define W5500_RSTGPIO_PIN_4 //定义W5500的RST引脚 #define W5500_RST_PORTGPIOA #define W5500_INTGPIO_PIN_4 //定义W5500的INT引脚 #define W5500_INT_PORTGPIOC#define W5500_SCSGPIO_PIN_5 //定义W5500的CS引脚 #define W5500_SCS_PORTGPIOC #define W5500_SCKGPIO_PIN_5 //定义W5500的SCK引脚 #define W5500_SCK_PORTGPIOA#define W5500_MISOGPIO_PIN_6 //定义W5500的MISO引脚#define W5500_MISO_PORTGPIOA#define W5500_MOSIGPIO_PIN_7 //定义W5500的MOSI引脚#define W5500_MOSI_PORTGPIOA/***************----- W5500 引脚输出电平控制 -----***************/#define W5500_CS_1() HAL_GPIO_WritePin(W5500_SCS_PORT, W5500_SCS, GPIO_PIN_SET); //将CS引脚设置为高电平#define W5500_CS_0() HAL_GPIO_WritePin(W5500_SCS_PORT, W5500_SCS, GPIO_PIN_RESET); //将CS引脚设置为低电平 #define W5500_RST_1() HAL_GPIO_WritePin(W5500_RST_PORT, W5500_RST, GPIO_PIN_SET); #define W5500_RST_0() HAL_GPIO_WritePin(W5500_RST_PORT, W5500_RST, GPIO_PIN_RESET); #define W5500_SCK_1() HAL_GPIO_WritePin(W5500_SCK_PORT, W5500_SCK, GPIO_PIN_SET); #define W5500_SCK_0() HAL_GPIO_WritePin(W5500_SCK_PORT, W5500_SCK, GPIO_PIN_RESET);#define W5500_MISO_1() HAL_GPIO_WritePin(W5500_MISO_PORT, W5500_MISO, GPIO_PIN_SET); #define W5500_MISO_0() HAL_GPIO_WritePin(W5500_MISO_PORT, W5500_MISO, GPIO_PIN_RESET);#define W5500_MOSI_1() HAL_GPIO_WritePin(W5500_MOSI_PORT, W5500_MOSI, GPIO_PIN_SET); #define W5500_MOSI_0() HAL_GPIO_WritePin(W5500_MOSI_PORT, W5500_MOSI, GPIO_PIN_RESET);/***************----- W5500 引脚初始化 -----***************//** * @description: W5500引脚初始化 * @return {*} */void W5500_IO_Init(void);/***************----- W5500 MCU-SPI驱动(被注册的API) -----***************//******************************************************************************** 函数名 : SPI_Write_Byte* 描述 : 写1字节数据到SPI总线* 输入 : TxData 写到总线的数据* 输出 : 无* 返回值 : 无* 说明 : 注册到——void wizchip_spi_writebyte(uint8_t wb) {}*******************************************************************************/void SPI_Write_Byte(uint8_t TxData);/******************************************************************************** 函数名 : SPI_Read_Byte* 描述 : 从SPI总线读取1字节数据* 输入 : 无* 输出 : 无* 返回值 : 读到的数据* 说明 : 注册——uint8_t wizchip_spi_readbyte(void) {return 0;}*******************************************************************************/uint8_t SPI_Read_Byte(void);/******************************************************************************** 函数名 : SPI_WriteBurst_Byte* 描述 : spi大量写函数,从pBuf往spi写len个字节* 输入 : ...* 输出 : 无* 返回值 : 无* 说明 : 注册到——void wizchip_spi_writeburst(uint8_t* pBuf, uint16_t len) {}*******************************************************************************/void SPI_WriteBurst_Byte(uint8_t* pBuf, uint16_t len);/******************************************************************************** 函数名 : SPI_ReadBurst_Byte* 描述 : spi大量读函数,从spi读取len个字节到pBuf* 输入 : ...* 输出 : 无* 返回值 : 读到的数据* 说明 : 注册——uint8_t wizchip_spi_readbyte(void) {return 0;}*******************************************************************************/void SPI_ReadBurst_Byte(uint8_t* pBuf, uint16_t len);/******************************************************************************** 函数名 : SPI_Cris_Enter* 描述 : 进入临界区* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 关闭STM32全局中断,用了操作系统(多线程)需要注册*******************************************************************************/void SPI_Cris_Enter(void);/******************************************************************************** 函数名 : SPI_Cris_Exit* 描述 : 退出临界区* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 关闭STM32全局中断,用了操作系统(多线程)需要注册*******************************************************************************/void SPI_Cris_Exit(void);/******************************************************************************** 函数名 : SPI_CS_Select* 描述 : 选定,片选信号输出低电平* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 无*******************************************************************************/void SPI_CS_Select(void);/******************************************************************************** 函数名 : SPI_CS_Deselect* 描述 : 取消选定,片选信号输出高电平* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 无*******************************************************************************/void SPI_CS_Deselect(void);#endif // __W5500_spi_h__
2.3. 将当前MCU的SPI驱动注册到W5500驱动中:
/******************************************************************************** 函数名 : W5500_API_Register* 描述 : W5500驱动的注册初始化函数* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : 官方库-需要将当前mcu的spi功能函数注册到驱动中(适配各个mcu)*******************************************************************************/void W5500_API_Register(void){ /* 注册驱动函数 */ /* 1、注册进入\\离开临界区函数 */ // cris_en 进入 // cris_ex 离开 reg_wizchip_cris_cbfunc(SPI_Cris_Enter, SPI_Cris_Exit); /* 2、注册spi片选函数 */ // cs_sel 选定 // cs_desel 取消选定 reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); /* 3、注册spi读写单字节函数 */ // spi_rb 从spi读一个字节 // spi_wb 往spi写一个字节 reg_wizchip_spi_cbfunc(SPI_Read_Byte, SPI_Write_Byte); /* 4、注册spi大量读写函数 */ // spi_rb 从spi读取len个字节到pBuf // spi_wb 从pBuf往spi写len个字节 reg_wizchip_spiburst_cbfunc(SPI_ReadBurst_Byte, SPI_WriteBurst_Byte);}
W5500现有库分析:【后续有时间弄,暂时是半成品】
W5500 | Mbed
寄存器开发
w5500.c/.h - 封装了读写寄存器的操作/存放了从REG取值或将值写入REG的基本输入/输出函数。
w5500.h中存放的是各种关于地址的宏定义、关于寄存器读取写入的宏定义、函数声明等。
核心:读写w5500寄存器
封装函数开发
wizchip_conf.c 和wizchip_conf.h
socket.c.h
一文掌握w5500以太网芯片-CSDN博客
所有API都有封装好的,非直接显示对寄存器的
setSHAR(mac);setGAR(gw);setSUBR(sn);setSIPR(sip);I = getIR():清除 WIZCHIP 的中断。void wizchip_clrinterrupt (intr_kind intr)获取 WIZCHIP 的中断。intr_kind wizchip_getinterrupt (void)初始化:int8_t wizchip_init(uint8_t* txsize, uint8_t* rxsize)
两种开发方式之间的联系:
封装函数的底层是寄存器开发
1. 封装读写寄存器
2. 封装模块功能 - 分析
2.1. 初始化
封装了寄存器初始化过程1.ctlwizchip(){ int8_t wizchip_init(uint8_t* txsize, uint8_t* rxsize) { wizchip_sw_reset(); setSn_RXBUF_SIZE(i, j); setSn_RXBUF_SIZE(i, rxsize[i]); //.... }}2.#define setSn_RXBUF_SIZE(sn, rxbufsize) \\WIZCHIP_WRITE(Sn_RXBUF_SIZE(sn),rxbufsize)3.void WIZCHIP_WRITE(uint32_t AddrSel, uint8_t wb ){ uint8_t spi_data[4]; WIZCHIP_CRITICAL_ENTER(); WIZCHIP.CS._select(); AddrSel |= (_W5500_SPI_WRITE_ | _W5500_SPI_VDM_OP_); //if(!WIZCHIP.IF.SPI._read_burst || !WIZCHIP.IF.SPI._write_burst) // byte operation if(!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);WIZCHIP.IF.SPI._write_byte(wb); } else// burst operation {spi_data[0] = (AddrSel & 0x00FF0000) >> 16;spi_data[1] = (AddrSel & 0x0000FF00) >> 8;spi_data[2] = (AddrSel & 0x000000FF) >> 0;spi_data[3] = wb;WIZCHIP.IF.SPI._write_burst(spi_data, 4); } WIZCHIP.CS._deselect(); WIZCHIP_CRITICAL_EXIT();}
2.2. 中断
W5500中断框架:{ 依次分析以下寄存器 1. IR 2. SIR 2. Sn_IR}// 获取中断源1. 封装了IR SIR的获取 >> 可以直接得到中断源intr_kind wizchip_getinterrupt(void){ uint8_t ir = 0; uint8_t sir = 0; uint16_t ret = 0; ir = getIR(); sir = getSIR(); ret = sir; ret = (ret <> 获得详细中断类型getSn_IR(sir);
开发:
W5500简单使用及官方IO库 快速入门_w5500 io-p实例-CSDN博客
主要源代码:
socket.h/c
:使用模块
则提供了类berkeley socket api的接口函数,直接用于驱动TCP/IP功能。其中大量调用另外两个文件中提供的驱动函数,可以将其视作更高层的抽象。当然,抽象是要付出代价的,如果希望省点代码的话,可以直接调用真正的底层函数。
wizchip_conf.h
:配置模块
两者关系:
socket.h
中的部分api,其实可以调用wizchip_conf.h
中的,两者等效,后者底层
新版本把这些api封装回wizchip_conf中了,下面的socket.h函数基本都在wiz里
驱动核心:配置->使用
驱动框架
wizchip_conf.h
中:
1.1. 物理层配置——int8_t ctlwizchip(ctlwizchip_type cwtype, void* arg)
初始化、重置、定制缓冲区大小;获取/清楚中断
int8_t ctlwizchip(ctlwizchip_type cwtype, void* arg);
该函数封装了基本所有功能(设置和读取网络配置除外),通过传入参数选择功能
功能
软重置W5500,然后根据参数重置每个端口的收发缓冲区大小
详细:控制WIZCHIP芯片,重置WIZCHIP和内部PHY,配置PHY模式,监视PHY(链接,速度,半/全/自动),控制中断和屏蔽等。
第一个参数
ctlwizchip_type cwtype
枚举值
第二个参数
void* arg
返回值
返回0为成功,-1为失败
详细信息:(芯片数据手册可查)
第一个参数,枚举——ctlnetwork_type
:
函数原型:
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;}
1.2. 网络层配置——int8_t ctlnetwork(ctlnetwork_type cntype, void* arg)
int8_t ctlnetwork(ctlnetwork_type cntype, void* arg);
功能
控制网络,控制网络环境,模式,超时等
第一个参数
ctlnetwork_type cntype
枚举值
第二个参数
void* arg
返回值
返回0为成功,-1为失败
详细信息:(芯片数据手册可查)
第一个参数,枚举——ctlnetwork_type
:
函数原型:
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;}
socket.h
中:
1.1. 配置并打开Socket sn——int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag)
注:打开socket前先配置好网络参数/网络层配置。
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);
描述
按照传递的参数初始化并打开socket sn
参数:sn
socket号(0-7)
参数:protocol
指定要运行的协议类型(Sn_MR_XXX)
参数:port
绑定的端口号,如果为0则自动分配
参数:flag
socket flags,见SF_XXXXXXX
返回:
- sn 如果成功
- SOCKERR_SOCKNUM 如果socket号无效
- SOCKERR_SOCKMODE 不支持的socket模式
- SOCKERR_SOCKFLAG 无效的socket flags.
详细信息:(芯片数据手册可查)
第二个参数protocol枚举值:
/* Sn_MR Default values */@简要 支持UDP组播@详细说明 0 : 禁用组播\\n 1 : 启用组播\\n 此位仅在UDP模式下应用(P[3:0] = 010).\\n 要使用组播功能,需在通过@ref Sn_CR的OPEN命令打开Socket n之前,分别使用@ref Sn_DIPR和@ref Sn_DPORT配置多播组IP地址和端口号。之后的分析去看数据手册#define Sn_MR_MULTI 0x80/** * @brief Broadcast block in UDP Multicasting. * @details 0 : disable Broadcast Blocking\\n * 1 : enable Broadcast Blocking\\n * This bit blocks to receive broadcasting packet during UDP mode(P[3:0] = 010.\\m * In addition, This bit does when MACRAW mode(P[3:0] = 100 */#define Sn_MR_BCASTB 0x40/** * @brief No Delayed Ack(TCP), Multicast flag * @details 0 : Disable No Delayed ACK option\\n * 1 : Enable No Delayed ACK option\\n * This bit is applied only during TCP mode (P[3:0] = 001.\\n * When this bit is It sends the ACK packet without delay as soon as a Data packet is received from a peer.\\n * When this bit is It sends the ACK packet after waiting for the timeout time configured by @ref _RTR_. */#define Sn_MR_ND 0x20/** * @brief Unicast Block in UDP Multicasting * @details 0 : disable Unicast Blocking\\n * 1 : enable Unicast Blocking\\n * This bit blocks receiving the unicast packet during UDP mode(P[3:0] = 010 and MULTI = */#define Sn_MR_UCASTB 0x10/** * @brief MAC LAYER RAW SOCK * @details This configures the protocol mode of Socket n. * @note MACRAW mode should be only used in Socket 0. */#define Sn_MR_MACRAW 0x04#define Sn_MR_IPRAW 0x03 /**< IP LAYER RAW SOCK *//** * @brief UDP * @details This configures the protocol mode of Socket n. */UDP模式/协议#define Sn_MR_UDP 0x02/** * @brief TCP * @details This configures the protocol mode of Socket n. */TCP模式/协议#define Sn_MR_TCP 0x01/** * @brief Unused socket * @details This configures the protocol mode of Socket n. */关闭该socket#define Sn_MR_CLOSE 0x00/* Sn_MR values used with Sn_MR_MACRAW *//** * @brief MAC filter enable in @ref Sn_MR_MACRAW mode * @details 0 : disable MAC Filtering\\n * 1 : enable MAC Filtering\\n * This bit is applied only during MACRAW mode(P[3:0] = 100.\\n * When set as W5500 can only receive broadcasting packet or packet sent to itself. * When this bit is W5500 can receive all packets on Ethernet. * If user wants to implement Hybrid TCP/IP stack, * it is recommended that this bit is set as for reducing host overhead to process the all received packets. */#define Sn_MR_MFEN Sn_MR_MULTI/** * @brief Multicast Blocking in @ref Sn_MR_MACRAW mode * @details 0 : using IGMP version 2\\n * 1 : using IGMP version 1\\n * This bit is applied only during UDP mode(P[3:0] = 010 and MULTI = * It configures the version for IGMP messages (Join/Leave/Report). */#define Sn_MR_MMB Sn_MR_ND/** * @brief IPv6 packet Blocking in @ref Sn_MR_MACRAW mode * @details 0 : disable IPv6 Blocking\\n * 1 : enable IPv6 Blocking\\n * This bit is applied only during MACRAW mode (P[3:0] = 100. It blocks to receiving the IPv6 packet. */#define Sn_MR_MIP6B Sn_MR_UCASTB/* Sn_MR value used with Sn_MR_UDP & Sn_MR_MULTI *//** * @brief IGMP version used in UDP mulitcasting * @details 0 : disable Multicast Blocking\\n * 1 : enable Multicast Blocking\\n * This bit is applied only when MACRAW mode(P[3:0] = 100. It blocks to receive the packet with multicast MAC address. */#define Sn_MR_MC Sn_MR_ND
第四个参数flag枚举值:
/* * SOCKET FLAG */#define SF_ETHER_OWN (Sn_MR_MFEN) ///< In @ref Sn_MR_MACRAW, Receive only the packet as broadcast, multicast and own packet#define SF_IGMP_VER2 (Sn_MR_MC) ///< In @ref Sn_MR_UDP with \\ref SF_MULTI_ENABLE, Select IGMP version 2. #define SF_TCP_NODELAY (Sn_MR_ND) ///< In @ref Sn_MR_TCP, Use to nodelayed ack.#define SF_MULTI_ENABLE (Sn_MR_MULTI) ///< In @ref Sn_MR_UDP, Enable multicast mode.#if _WIZCHIP_ == 5500 #define SF_BROAD_BLOCK (Sn_MR_BCASTB) ///< In @ref Sn_MR_UDP or @ref Sn_MR_MACRAW, Block broadcast packet. Valid only in W5500 #define SF_MULTI_BLOCK (Sn_MR_MMB) ///< In @ref Sn_MR_MACRAW, Block multicast packet. Valid only in W5500 #define SF_IPv6_BLOCK (Sn_MR_MIP6B) ///< In @ref Sn_MR_MACRAW, Block IPv6 packet. Valid only in W5500 #define SF_UNI_BLOCK (Sn_MR_UCASTB) ///< In @ref Sn_MR_UDP with \\ref SF_MULTI_ENABLE. Valid only in W5500#endif#define SF_IO_NONBLOCK 0x01 ///< Socket nonblock io mode. It used parameter in \\ref socket().
函数原型:
1.2. 读取Socket sn状态机——getSn_SR(sn); / uint8_t WIZCHIP_READ(uint32_t AddrSel)
控制各个socket的基本方法就是读取对应socket的当前状态,然后据此进行各种处理,比如打开socket、开启监听、断连等。读取socket状态机的方法是读取状态寄存器SR,即以下函数
getSn_SR(sn);
其返回值见下表,为了方便理解,描述中直接改为了io库中的函数:
状态
简述
描述
SOCK_CLOSED
socket处于关闭状态,资源被释放。disconnect或close命令生效后,或者超时后,无视之前状态变为这个状态
此时无法通信
SOCK_INIT
socket以TCP模式打开,然后才可以调用connect或listen。通过正确地调用socket函数以转变为这个状态
SOCK_LISTEN
socket正以TCP服务器模式运作,并正在等待(监听)连接请求
SOCK_SYNSENT
socket发送了一个连接请求包(SYN包),这是从SOCK_INIT使用connect命令后的中间状态,如果随后收到了“接受连接”(SYN/ACK包),则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位
SOCK_SYNRECV
socket接收到了“请求连接”(SYN包),如果随后发送答复(SYN/ACK包)成功,则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位。
SOCK_ESTABLISHED
socket tcp连接已建立,即在SOCK_LISTEN状态下收到了tcp客户端发来的SYN包并答复成功,或使用connect命令成功后会转变为的状态。
SOCK_FIN_WAIT
SOCK_CLOSING
SOCK_TIME_WAIT
表明socket正在关闭。它们是tcp链接主动或被动关闭的中间状态。
SOCK_CLOSE_WAIT
表明socket正在关闭。这个状态说明socket收到了tcp链接的另一方发来的“断连请求”(FIN包)。这是半关闭状态,可以继续发送数据。发送完后应该调用disconnect或者close来完全关闭。
SOCK_LAST_ACK
表明socket正在被动关闭状态下。这个状态说明socket正在等待对“断连请求”(FIN包)的答复(FIN/ACK包)。当成功收到答复或者超时后会变为SOCK_CLOSED状态。
SOCK_UDP
socket正以UDP模式运作。通过正确地调用socket函数以转变为这个状态
SOCK_IPRAW
IP raw模式。本文不涉及这方面内容。
SOCK_MACRAW
MACRAW模式。本文不涉及这方面内容。
详细信息:(芯片数据手册可查)
函数原型:
/** * @ingroup Socket_register_access_function * @brief Get @ref Sn_SR register * @param (uint8_t)sn Socket number. It should be 0 ~ 7. * @return uint8_t. Value of @ref Sn_SR. */#define getSn_SR(sn) \\WIZCHIP_READ(Sn_SR(sn))说明getSn_SR就是调用的底层APIuint8_t WIZCHIP_READ(uint32_t AddrSel){ uint8_t ret; uint8_t spi_data[3]; WIZCHIP_CRITICAL_ENTER(); WIZCHIP.CS._select(); 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;}
枚举值、宏、结构体:
网络参数-结构体:
首先根据提供的结构体,将我们要定义的网络配置信息进行赋值:
static wiz_NetInfo NetConf = { .mac = {0x0c, 0x29, 0xab, 0x7c, 0x04, 0x02}, //mac地址 .ip = {192, 168, 0, 215}, //本机IP地址 .sn = {255, 255, 255, 0}, //子网掩码 .gw = {192, 168, 0, 1}, //网关地址 .dns = {0, 0, 0, 0}, //DNS服务器地址 .dhcp = NETINFO_STATIC //使用静态IP};
这个结构体存在于wizchip_conf.c,其中NetConf是一个wiz_NetInfo类型的结构体变量,该结构体在wizchip_conf.h中定义,用于设置mac地址、IP地址等网络参数,具体如下:
typedef struct wiz_NetInfo_t{ uint8_t mac[6]; ///< Source Mac Address uint8_t ip[4]; ///< Source IP Address uint8_t sn[4]; ///< Subnet Mask uint8_t gw[4]; ///< Gateway IP Address uint8_t dns[4]; ///< DNS server IP Address dhcp_mode dhcp; ///< 1 - Static, 2 - DHCP}wiz_NetInfo;
补充:
- sn的Socket号(0-7)
常用API讲解:
1. 初始化:
1.1. 初始化W5500
socket.h
中:
uint8_t ar[16] = {2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2}; // 全部收发缓冲区设为2KB(默认)ctlwizchip(CW_INIT_WIZCHIP,ar);
返回值0为成功,-1为失败,之后使用到这个函数时不再说明。
其会软重置W5500,然后根据参数重置每个端口的收发缓冲区大小。
第二个参数的类型为uint8_t [2][8],分别指定8个端口上收和发缓冲区的大小(KB),收和发分别加起来不能超过16K。默认每个端口的收发端口分别为2K。
对应wizchip_conf.h
中
int8_t wizchip_init(uint8_t* txsize, uint8_t* rxsize)
txsize和rxsize都为uint8_t[8]
2. 中断初始化 与 中断开发
2.1.1. Sn_MR寄存器(Socket n 模式寄存器)
主要是TCP延时/无延时
IINCHIP_WRITE(Sn_MR(7), 0x20);//TCP模式下关闭无延时ACK
2.1. 中断初始化 -【初始化3个中断屏蔽寄存器】
- 参考:
2.1.1. IMR寄存器(中断屏蔽寄存器) - 使能芯片中断
/* IMR register values *//** * @brief IP Conflict Interrupt Mask. * @details 0: Disable IP Conflict Interrupt\\n * 1: Enable IP Conflict Interrupt */#define IM_IR7 0x80/** * @brief Destination unreachable Interrupt Mask. * @details 0: Disable Destination unreachable Interrupt\\n * 1: Enable Destination unreachable Interrupt */#define IM_IR6 0x40/** * @brief PPPoE Close Interrupt Mask. * @details 0: Disable PPPoE Close Interrupt\\n * 1: Enable PPPoE Close Interrupt */#define IM_IR5 0x20/** * @brief Magic Packet Interrupt Mask. * @details 0: Disable Magic Packet Interrupt\\n * 1: Enable Magic Packet Interrupt */#define IM_IR4 0x10
2.1.2. SIMR寄存器(Socket中断屏蔽寄存器) - 使能Socket n中断的
- 程序中 - 宏定义值:
直接给sn
2.1.3. Sn_IMR寄存器(中断屏蔽寄存器) - 使能Socket n的事件中断(根据需求)
- Sn_IMR的宏定义和Sn_IR一致
- 程序中 - 宏定义值:
/* Sn_IR values *//* Sn_IR/Sn_IMR寄存器的值 - Socket n中断寄存器*//** * @brief SEND_OK Interrupt// 发送完成中断 * @details This is issued when SEND command is completed. */#define Sn_IR_SENDOK 0x10/** * @brief TIMEOUT Interrupt// 超时中断 * @details This is issued when ARPTO or TCPTO occurs. */#define Sn_IR_TIMEOUT 0x08/** * @brief RECV Interrupt// 接收到对方数据中断 * @details This is issued whenever data is received from a peer. */#define Sn_IR_RECV 0x04/** * @brief DISCON Interrupt// (当收到对方的断开申请)断开连接中断 * @details This is issued when FIN or FIN/ACK packet is received from a peer. */#define Sn_IR_DISCON 0x02/** * @brief CON Interrupt// 与对方成功建立连接中断 * @details This is issued one time when the connection with peer is successful and then @ref Sn_SR is changed to @ref SOCK_ESTABLISHED. */#define Sn_IR_CON 0x01
2.2. 中断开发
W5500提供了中断机制来提高MCU响应的实时性。
使用中断机制来设计与W5500通讯程序属于较高级内容,这里不展开。但是要注意的是,如果使用中断且用了官方的IO库,要避开SENDOK和TIMEOUT中断,这两个中断被IO库内部使用,如果自己清零的话会导致库的运行不正常。
- 开启引脚中断 W5500_INT(CubeMX)
- 程序中,初始化W5500中断【2.1.中】
- 基于三个中断寄存器,开发程序
2.2.1. IR寄存器(中断寄存器)
- 程序中 - 宏定义值:
/* IR寄存器值*//** * @brief Check IP conflict.//IP冲突 * @details Bit is set as when own source IP address is same with the sender IP address in the received ARP request. */#define IR_CONFLICT 0x80/** * @brief Get the destination unreachable message in UDP sending.//目标不可抵达 * @details When receiving the ICMP (Destination port unreachable) packet, this bit is set as * When this bit is Destination Information such as IP address and Port number may be checked with the corresponding @ref UIPR & @ref UPORTR. */#define IR_UNREACH 0x40/** * @brief Get the PPPoE close message.//PPPoE连接关闭 * @details When PPPoE is disconnected during PPPoE mode, this bit is set. */#define IR_PPPoE 0x20/** * @brief Get the magic packet interrupt.//Magic Packet * @details When WOL mode is enabled and receives the magic packet over UDP, this bit is set. */#define IR_MP 0x10
2.2.2. SIR寄存器(Socket 中断寄存器)——判断哪个Socket发生中断
注意:写零Sn_IR对应的SIR寄存器位也会清零!
- 程序中 - 宏定义值:
直接给sn
2.2.3. Sn_IR寄存器(Socket n 中断寄存器)——判断发生中断的Socket具体是什么中断(建立、发送、中止、接收、超时)
- 程序中 - 宏定义值:
这个枚举值和Sn_IR寄存器值宏定义一致
3. 设置socket的工作模式、IO模式、中断和掩码,或获取套接字缓冲区信息
3.1. ctlsocket - 函数原型:
int8_t ctlsocket (uint8_t sn, ctlsock_type cstype, void * arg );
Control socket.
Control IO mode, Interrupt & Mask of socket and get the socket buffer information. Refer to ctlsock_type.
Parameters:
sn
socket number
cstype
type of control socket. refer to ctlsock_type
.
arg
Data type and value is determined according to ctlsock_type
.
3.2. setsockopt()/getsockopt() - 函数原型:
setsockopt()getsockopt()
4. 重置
软件重置
软重置W5500使用socket.h
中
ctlwizchip(CW_RESET_WIZCHIP,(void *)0);
相当于直接调用wizchip_conf.h
中
void wizchip_sw_reset(void);
实际上,如果不需要定制缓冲区大小的话,直接把软重置当做初始化来用就行。
硬件重置
/******************************************************************************** 函数名 : W5500_Hardware_Reset* 描述 : 硬件复位W5500* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : W5500的复位引脚保持低电平至少500us以上,才能重围W5500*******************************************************************************/void W5500_Hardware_RESET(void){ W5500_RST_W(0); mdelay(50); W5500_RST_W(1); mdelay(200); //这里给50ms/200ms // while((Read_W5500_1Byte(PHYCFGR)&LINK)==0); //等待以太网连接完成}
5. 获取芯片ID
这是个毫无意义的功能,但还是蛮提一下。
char id[6];ctlwizchip(CW_GET_ID,(void *)id);
id里是固定的 “W5500”。
6. 设置和读取网络配置
(socket.h中)
ctlnetwork(CN_SET_NETINFO,(void *)&conf);//设置网络状态ctlnetwork(CN_GET_NETINFO,(void *)&conf);//读取网络状态
分别是设置和获取网络配置,其参数为指向下面结构的指针
typedef struct wiz_NetInfo_t{ uint8_t mac[6]; ///< Source Mac Address uint8_t ip[4]; ///< Source IP Address uint8_t sn[4]; ///< Subnet Mask uint8_t gw[4]; ///< Gateway IP Address uint8_t dns[4]; ///< DNS server IP Address dhcp_mode dhcp; ///< 1 - Static, 2 - DHCP}wiz_NetInfo;
分别相当于直接调用(wizchip_conf.h中)
void wizchip_setnetinfo(wiz_NetInfo* pnetinfo);void wizchip_getnetinfo(wiz_NetInfo* pnetinfo);
ctlnetwork的返回值并不能判断是否设置成功,所以一般在设置之后我们会立刻回读配置以确定正确设置,如二者一致,则说明正确设置,否则就得检查是不是通讯哪里出问题了。
……static wiz_NetInfo NetConf = { {0x0c,0x29,0xab,0x7c,0x04,0x02}, // mac地址 {192,168,1,133}, // 本地IP地址 {255,255,255,0}, // 子网掩码 {192,168,1,1}, // 网关地址 {0,0,0,0}, // DNS服务器地址 NETINFO_STATIC // 使用静态IP };void configNet(){ wiz_NetInfo conf; // 配置网络地址 ctlnetwork(CN_SET_NETINFO,(void *)&NetConf); // 回读 ctlnetwork(CN_GET_NETINFO,(void *)&conf); if(memcmp(&conf,&NetConf,sizeof(wiz_NetInfo)) == 0){ // 配置成功 }else{ // 配置失败 }}
需要注意的是:
虽然wiz_NetInfo结构中有DNS呀、网关呀什么的可选,但实际上这些功能都需要外挂其他程序才能实现,否则没有任何意义。想要直接能用的话就把它当静态IP老老实实设置mac地址、本地IP地址,子网掩码、网关地址。
7. 配置超时行为
如发起tcp链接、发送数据等的时候,如果一段时间没有答复(即超过超时时间),W5500会重发,直到超过重试次数还没有得到答复,就会触发超时中断。
如果需要配置超时行为:
wiz_NetTimeout to;to.retry_cnt = 8; // 重试次数,默认8次to.time_100us = 2000; // 超时时间,默认2000*100us = 200mswizchip_settimeout(&to);
等价于调用socket.h中的:
ctlnetwork(CN_SET_TIMEOUT,(void *)&to);
当然,也都有对应的get方法,就是把set改为get。
8. 打开socket*
在“驱动核心”中介绍了
9. 读取socket状态机*
在“驱动核心”中介绍了
高阶开发API:
W5500简单使用及官方IO库 快速入门_w5500 io-p实例-CSDN博客
[Windows CMD] 查看网络连接状态 netstat -na | findstr “TCP“_windows netstat -ano|findstr-CSDN博客
- 配置并打开Socket sn——
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag)
注:打开socket前先配置好网络参数/网络层配置。
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);
描述
按照传递的参数初始化并打开socket sn
参数:sn
socket号(0-7)
参数:protocol
指定要运行的协议类型(Sn_MR_XXX)
参数:port
绑定的端口号,如果为0则自动分配
参数:flag
socket flags,见SF_XXXXXXX
返回:
- 如果成功 返回socket sn号
- SOCKERR_SOCKNUM 如果socket号无效
- SOCKERR_SOCKMODE 不支持的socket模式
- SOCKERR_SOCKFLAG 无效的socket flags.
- 读取Socket sn状态机——
getSn_SR(sn); / uint8_t WIZCHIP_READ(uint32_t AddrSel)
控制各个socket的基本方法就是读取对应socket的当前状态,然后据此进行各种处理,比如打开socket、开启监听、断连等。读取socket状态机的方法是读取状态寄存器SR,即以下函数
getSn_SR(sn);
其返回值见下表,为了方便理解,描述中直接改为了io库中的函数:
状态
描述
SOCK_CLOSED
socket处于关闭状态,资源被释放。disconnect或close命令生效后,或者超时后,无视之前状态变为这个状态
此时无法通信
SOCK_INIT
socket以TCP模式打开,然后才可以调用connect或listen。通过正确地调用socket函数以转变为这个状态
SOCK_LISTEN
socket正以TCP服务器模式运作,并正在等待(监听)连接请求
SOCK_SYNSENT
socket发送了一个连接请求包(SYN包),这是从SOCK_INIT使用connect命令后的中间状态,如果随后收到了“接受连接”(SYN/ACK包),则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位
SOCK_SYNRECV
socket接收到了“请求连接”(SYN包),如果随后发送答复(SYN/ACK包)成功,则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位。
SOCK_ESTABLISHED
socket tcp连接已建立,即在SOCK_LISTEN状态下收到了tcp客户端发来的SYN包并答复成功,或使用connect命令成功后会转变为的状态。
SOCK_FIN_WAIT
SOCK_CLOSING
SOCK_TIME_WAIT
表明socket正在关闭。它们是tcp链接主动或被动关闭的中间状态。
SOCK_CLOSE_WAIT
表明socket正在关闭。这个状态说明socket收到了tcp链接的另一方发来的“断连请求”(FIN包)。这是半关闭状态,可以继续发送数据。发送完后应该调用disconnect或者close来完全关闭。
SOCK_LAST_ACK
表明socket正在被动关闭状态下。这个状态说明socket正在等待对“断连请求”(FIN包)的答复(FIN/ACK包)。当成功收到答复或者超时后会变为SOCK_CLOSED状态。
SOCK_UDP
socket正以UDP模式运作。通过正确地调用socket函数以转变为这个状态
SOCK_IPRAW
IP raw模式。本文不涉及这方面内容。
SOCK_MACRAW
MACRAW模式。本文不涉及这方面内容。
TCP模式 - TCP socket(1)
- 配置 socket sn 为TCP模式,并绑定xxxx端口,最后打开该socket
SF_TCP_NODELAY
指定socket在收到对方的数据包后应该没有延时尽快答复ACK包,否则需要超时时间做延时。SF_IO_NONBLOCK
用于控制socket.h中函数的行为,如启用这一选项,对这一socket调用socket.h中大部分函数不会阻塞等待调用结果,而是会在确认发出指令后尽快返回。
要注意的是,启用后,大部分函数的返回值会为SOCK_BUSY,这并不代表调用就失败了
会影响所有W5500API的工作
if (socket(sn, Sn_MR_TCP, xxxx, SF_TCP_NODELAY | SF_IO_NONBLOCK) == sn){ // 打开成功}else{ // 打开失败}
TCP服务器 - TCP socket开始监听(2)
- 在成功把socket配置为tcp模式后,通过调用listen来打开监听,即作为tcp服务器:
// 描述: 监听来自客户端的连接请求// 参数: sn socket号(0-7)// 返回: SOCK_OK 如果成功// SOCKERR_SOCKINIT 如果还未初始化socket// SOCKERR_SOCKCLOSED 如果socket意外关闭int8_t listen(uint8_t sn);
示例:
listen(sn);
TCP客户端 - TCP socket连接到TCP服务器(2)
- 在成功把socket配置为tcp模式后,通过调用connect来发起tcp链接:
// 描述: 尝试连接一个tcp服务器// 参数: sn socket号(0-7)// addr 目标IP地址(uint_8[4])// port 目标端口号// 返回: SOCK_OK 如果成功// SOCKERR_SOCKNUM 无效的socket号// SOCKERR_SOCKMODE socket模式无效// SOCKERR_SOCKINIT 如果还未初始化socket// SOCKERR_IPINVALID IP地址错误// SOCKERR_PORTZERO port参数为0// SOCKERR_TIMEOUT 连接超时// SOCK_BUSY 非阻塞模式下立即返回此值// 注意:1. 仅在tcp模式下有效// 2. 在阻塞io(默认)模式下,直到确认连接完成才会返回// 3. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,会立即返回SOCK_BUSYint8_t connect(uint8_t sn, uint8_t * addr, uint16_t port);
示例:
uint8_t ipAddr[4] = {192,168,2,30};connect(1,ipAddr,8080);
TCP模式 - 发送数据(3)
在socket上的tcp链接成功建立后,可以调用send函数来发送数据。
// 描述: 发送数据给TCP socket上连接的对象// 参数: sn socket号(0-7)// buf 指向要发送的数据的缓冲区// len 缓冲区中数据的字节长度// 返回: 发送的字节长度 如果成功// SOCKERR_SOCKSTATUS 无效的socket状态// SOCKERR_TIMEOUT 发送超时// SOCKERR_SOCKMODE socket模式无效// SOCKERR_SOCKNUM 无效的socket号// SOCKERR_DATALEN len为0// SOCK_BUSY socket正忙// 注意:1. 仅在tcp服务器或客户端模式下有效,且无法发送大于socket发送缓冲区大小的数据// 2. 在阻塞io(默认)模式下,直到数据发送完成才会返回 — socket发送缓冲区大小比数据大// 3. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,当socket缓冲区不够用,会立即返回SOCK_BUSYint32_t send(uint8_t sn, uint8_t * buf, uint16_t len);
示例:
如往已经建立了tcp链接的socket 1上发送hello world:char buf[] = \"hello world!\";send(1,buf,strlen(buf));
TCP模式 - 接收数据(4)
在socket上的tcp链接成功建立后,可以调用recv函数来获得收到的数据。
- 注意,如果缓冲区大小比socket接收缓冲区小的话并不能保证一次调用就能接收完所有数据。所以常会循环接收并处理,直到返回值小于等于0。
// 描述: 接收TCP socket上连接的对象发来的数据// 参数: sn socket号(0-7)// buf 指向要接收数据的缓冲区// len 缓冲区的最大长度// 返回: 接收的字节长度 如果成功// SOCKERR_SOCKSTATUS 无效的socket状态【socket可能没有监听对象了/断开连接】// SOCKERR_SOCKMODE socket模式无效// SOCKERR_SOCKNUM 无效的socket号// SOCKERR_DATALEN len为0// SOCK_BUSY socket正忙// 注意:1. 仅在tcp服务器或客户端模式下有效,且无接收大于socket接收缓冲区大小的数据// 2. 在阻塞io(默认)模式下,如果暂时没有数据,就会不停阻塞等待直到收到任意数据或者链接断开。// 3. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,如果暂时没有数据可接收,会立刻返回SOCK_BUSYint32_t recv(uint8_t sn, uint8_t * buf, uint16_t len);
示例:
int32_t len;uint8_t buf[BUF_SIZE];while((len = recv(1,buf,BUF_SIZE)) > 0){ // 对刚收到的数据进行处理}
注意:
- 在阻塞io(默认)模式下,如果暂时没有数据,就会不停阻塞等待直到收到任意数据或者链接断开。
- 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,如果暂时没有数据可接收,会立刻返回SOCK_BUSY(会一直跑,W5500API不会阻塞程序)
TCP链接 - 自动心跳包检测(5)
对于TCP链接,如果意外断连,比如网线被拔了呀之类的,W5500很可能并没法知道实际已经没有链接了,然后就认为自己还连着,一直等待着数据,然后就很爆炸。
为此,需要加入心跳检测来保证确实通讯还正常。
- 开启自动心跳检测,在socket初始化为TCP模式后:
setSn_KPALVTR(sn,2); // 设置心跳包自动发送间隔,单位时间为5s,所以这里设置为10s。为0则不启用。
以上代码等价于:
uint8_t t = 2;setsockopt(sn, SO_KEEPALIVEAUTO, (void*)&t);
需要注意的是,KeepAlive包会在socket状态变为SOCK_ESTABLISHED且与对方至少进行过一次收或发的通讯后进行传输。所以建立链接后建议立即随便发点什么。
- 当然,也可以手动发送心跳包(没开启自动发送才行——第三个参数(发生间隔)给0):
setsockopt(sn, SO_KEEPALIVESEND, (void *)0);
TCP链接 - 断连TCP链接
如需要主动断开TCP链接,或者更多情况下是因为发现socket的状态为SOCK_CLOSE_WAIT半关闭状态,而需要断开连接。
则调用disconnect函数:
// 描述: 断开一个连接着的socket,附带关闭socket// 参数: sn socket号(0-7)// 返回: SOCK_OK 如果成功// SOCKERR_SOCKNUM 无效的socket号// SOCKERR_SOCKMODE socket模式无效// SOCKERR_TIMEOUT 发生超时// SOCK_BUSY socket正忙// 注意:1. 仅在tcp服务器或客户端模式下有效// 2. 在阻塞io(默认)模式下,直到断开完成才会返回// 3. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,会立即返回SOCK_BUSYint8_t disconnect(uint8_t sn);
UDP模式 - UDP socket
配置 socket sn 为UDP模式,并绑定xxxx端口,最后打开
SF_IO_NONBLOCK
用于控制socket.h中函数的行为,如启用这一选项,对这一socket调用socket.h中大部分函数不会阻塞等待调用结果,而是会在确认发出指令后尽快返回。
要注意的是,启用后,大部分函数的返回值会为SOCK_BUSY,这并不代表调用就失败了
if(socket(2, Sn_MR_UDP, 5555, SF_IO_NONBLOCK) == 2){ // 打开成功}else{ // 打开失败}
主要就是改了下协议。
SF_IO_NONBLOCK 作用同上。如果flags参数不需要的话直接传个0进去就行。
UDP模式 - 发送数据
socket初始化为udp模式后,发送数据需要使用sendto函数。
// 描述: 发送UDP或MACRAW数据报给参数指定的IP地址// 参数: sn socket号(0-7)// buf 指向要发送的数据的缓冲区// len 缓冲区中数据的字节长度// addr 目标IP地址,uint8_t[4]// port 目标端口号// 返回: 发送的字节长度 如果成功// SOCKERR_SOCKNUM 无效的socket号// SOCKERR_SOCKMODE socket模式无效// SOCKERR_SOCKSTATUS 无效的socket状态// SOCKERR_DATALEN len为0// SOCKERR_IPINVALID 错误的IP地址// SOCKERR_PORTZERO port为0// SOCKERR_SOCKCLOSED socket意外关闭// SOCKERR_TIMEOUT 发送超时// SOCK_BUSY socket正忙// 注意:1. 仅在tcp服务器或客户端模式下有效,且无法发送大于socket发送缓冲区大小的数据// 2. 在阻塞io(默认)模式下,直到数据发送完成才会返回 — socket发送缓冲区大小比数据大// 3. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,当socket缓冲区不够用,会立即返回SOCK_BUSYint32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port);
如使用初始化为udp的socket 2发送数据给192.168.2.30:3333:char buf[] = \"data!\";uint8_t ipAddr[4] = {192,168,2,30};sendto(2,buf,strlen(buf),ipAddr,3333);
UDP模式 - 接收数据
// 描述: 接收UDP或MACRAW数据报// 参数: sn socket号(0-7)// buf 指向要接收数据的缓冲区// len 缓冲区的最大长度,当大于数据包大小时,接收数据包大小的数据;小于时,接收len大小的数据。// addr 用于返回发送者ip地址,仅在对每个收的包第一次调用recv时有效。// port 用于返回发送者的端口号,仅在对每个收的包第一次调用recv时有效。// 返回: 实际接收的字节长度 如果成功// SOCKERR_DATALEN len为0// SOCKERR_SOCKMODE socket模式无效// SOCKERR_SOCKNUM 无效的socket号// SOCK_BUSY socket正忙// 注意:1. 在阻塞io(默认)模式下,如果暂时没有数据,就会不停阻塞等待直到收到任意数据。// 2. 在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,如果暂时没有数据可接收,会立刻返回SOCK_BUSYint32_t recvfrom(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port);
对于UDP这类无连接的协议,W5500并不会自动帮忙提取ip地址,而是直接把整个包交给用户去处理,所以发送方的ip地址实际上是软件的方式从每个数据报的报头处提取出来的。为了实现这个功能,io库内部有标志位来记录当前是不是一个新的包,如是,则提取地址并返回。这种解决方案也直接导致了每个包只有第一次调用recvfrom时能得到ip地址信息。
虽然库的英文注释中有说通过读取PACKINFO的方法来判断addr和port是否有效,但是通过阅读源码,好像这个方法并没有用。好在如果不是第一个包,函数内部并不会动addr和port指向的值。所以下面方法接收时能保证ip地址每次循环都是正确的。
int32_t len;uint8_t addr[4];uint8_t port;uint8_t buf[BUF_SIZE];while((len = recvfrom(1,buf,BUF_SIZE,addr,&port)) > 0){ // 对从addr:port收到的数据进行处理}
UDP广播
这其实是TCP/IP的知识点,不属于W5500使用上的问题。广播时根据需求构造目标IP地址
如果要全局广播,则发往255.255.255.255
如果要在192.168.2.0/24(即掩码为255.255.255.0)上广播,则发往192.168.2.255
具体代码就不写,和sendto那里的示例代码就差个ip地址具体的值。
UDP多播
发送数据给多播地址没什么可说的,就是设置为多播的ip地址就行。下面讲下加入多播地址。
加入多播地址需要在打开socket为UDP前设置DIPR和DPORT为对应的组播地址,然后打开socket时使能多播。
与WIN7通讯时发现要使用IGMP版本2才能成功通讯(即flag中启用SF_IGMP_VER2 )
加入组播地址后,socket就无法发送普通UDP包了,只能发送组播包。
要想在同个UDP端口发送普通UDP包必须重新开个socket。
示例代码:
// 加入组播地址为224.0.0.251:6000uint8_t ip[4] = {224,0,0,251};setSn_DIPR(sn,dAddr.ip);setSn_DPORT(sn,6000);socket(sn,Sn_MR_UDP,6000,SF_IO_NONBLOCK | SF_IGMP_VER2 | SF_MULTI_ENABLE);
通用 - 读取Socket sn状态机
- 读取Socket sn状态机——
getSn_SR(sn); / uint8_t WIZCHIP_READ(uint32_t AddrSel)
控制各个socket的基本方法就是读取对应socket的当前状态,然后据此进行各种处理,比如打开socket、开启监听、断连等。读取socket状态机的方法是读取状态寄存器SR,即以下函数
getSn_SR(sn);
其返回值见下表,为了方便理解,描述中直接改为了io库中的函数:
状态
描述
SOCK_CLOSED
socket处于关闭状态,资源被释放。disconnect或close命令生效后,或者超时后,无视之前状态变为这个状态
此时无法通信
SOCK_INIT
socket以TCP模式打开,然后才可以调用connect或listen。通过正确地调用socket函数以转变为这个状态
SOCK_LISTEN
socket正以TCP服务器模式运作,并正在等待(监听)连接请求
SOCK_SYNSENT
socket发送了一个连接请求包(SYN包),这是从SOCK_INIT使用connect命令后的中间状态,如果随后收到了“接受连接”(SYN/ACK包),则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位
SOCK_SYNRECV
socket接收到了“请求连接”(SYN包),如果随后发送答复(SYN/ACK包)成功,则会转为SOCK_ESTABLISHED;否则在超时后会转为SOCK_CLOSED,同时会设置超时中断标志位。
SOCK_ESTABLISHED
socket tcp连接已建立,即在SOCK_LISTEN状态下收到了tcp客户端发来的SYN包并答复成功,或使用connect命令成功后会转变为的状态。
SOCK_FIN_WAIT
SOCK_CLOSING
SOCK_TIME_WAIT
表明socket正在关闭。它们是tcp链接主动或被动关闭的中间状态。
SOCK_CLOSE_WAIT
表明socket正在关闭。这个状态说明socket收到了tcp链接的另一方发来的“断连请求”(FIN包)。这是半关闭状态,可以继续发送数据。发送完后应该调用disconnect或者close来完全关闭。
SOCK_LAST_ACK
表明socket正在被动关闭状态下。这个状态说明socket正在等待对“断连请求”(FIN包)的答复(FIN/ACK包)。当成功收到答复或者超时后会变为SOCK_CLOSED状态。
SOCK_UDP
socket正以UDP模式运作。通过正确地调用socket函数以转变为这个状态
SOCK_IPRAW
IP raw模式。本文不涉及这方面内容。
SOCK_MACRAW
MACRAW模式。本文不涉及这方面内容。
通用 - 获取链接对象的IP地址
UDP链接下,recvfrom(UDP的接收数据函数)中直接能获得对方的ip地址。
TCP链接,主要是TCP服务器,如果需要知道链接对象的ip地址的话。
uint8_t ip[4];uint16_t port;// 获得连接对象的ipgetSn_DIPR(sn,ip);// 获得连接对象的端口号port = getSn_DPORT(sn);
等价于调用socket.h中的:
uint8_t ip[4];uint16_t port;// 获得连接对象的ipgetsockopt(sn,SO_DESTIP,(void *)ip);// 获得连接对象的端口号getsockopt(sn,SO_DESTPORT,(void *)&port);
通用 - 关闭socket
如需要关闭socket,调用close函数:
// 描述: 关闭一个socket// 参数: sn socket号(0-7)// 返回: SOCK_OK 如果成功// SOCKERR_SOCKNUM 无效的socket号int8_t close(uint8_t sn);
其实调用socket的时候内部会先调用一次close函数,所以其实没必要先调用close来关闭socket。
【程序】开发基本流程:
1. 移植
上面的移植过程
1.1. 要有一个专门用来调试的串口,并绑定printf
1.2. 移植W5500基本功能,并完成注册函数编写
2. 编写硬件复位W5500函数,并实现W5500初始化函数 - (实现可ping)
STM32+W5500+以太网应用开发+001_Ping 新建工程,移植驱动_stm32 w5500 ping-CSDN博客
2.1. W5500日志输出
/** * @funticon_name: W5500_LOG * @brief W5500日志输出功能 * @return {*} * @note none * @param {char *} format */void W5500_LOG(char * format, ...){ #if SPI_W5500_LOG $(Debug_UART_data).UART_DMA_printf(format); mdelay(100); #endif}
2.2. 硬件复位W5500
/******************************************************************************** 函数名 : W5500_Hardware_Reset* 描述 : 硬件复位W5500* 输入 : 无* 输出 : 无* 返回值 : 无* 说明 : W5500的复位引脚保持低电平至少500us以上,才能重围W5500*******************************************************************************/void W5500_Hardware_RESET(void){ W5500_RST_W(0); mdelay(50); W5500_RST_W(1); mdelay(200); //这里给50ms/200ms // while((Read_W5500_1Byte(PHYCFGR)&LINK)==0); //等待以太网连接完成}
2.3. W5500初始化:
控制WIZCHIP芯片,重置WIZCHIP和内部PHY,配置PHY模式,监视PHY(链接,速度,半/全/自动),控制中断和屏蔽等。
/* 网络参数(wiz_NetInfo)-NetConf */wiz_NetInfo NetConf = { .mac = { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, //mac地址 .ip = { 192, 168, 1, 10 },//本机IP地址 .sn = { 255, 255, 255, 0 }, //子网掩码 .gw = { 192, 168, 1, 1 }, //网关地址 .dns = { 8, 8, 8, 8 }, //DNS服务器地址 .dhcp = NETINFO_STATIC //使用静态IP }; wiz_NetInfo conf; //读取出来的网络参数/** * @funticon_name:W5500_Init * @brief w5500总初始化函数 * @return {int8_t} * @note none */int8_t W5500_Init(void){/***************---- 物理层配置 ----*****************/ /* W5500-WHZCHIP-芯片初始化,初始化W5500中断 */ W5500_WIZCHIP_Init(); /* 配置与读取(NETWORK)网络参数 */ W5500_NETWORK_Init(); /* 配置与读取(PHY)以太网参数 */ W5500_PHY_Init();}
2.4. W5500中断初始化(中断开发核心)
----写在W5500_WIZCHIP_Init()最后;----/* 启动中断,参考W5500数据手册确定自己需要的中断类型 */// 开启Socket中断源 - Socket事件中断,根据需要添加setSn_IMR( Main_Socket_data.Sn, Sn_IR_SENDOK | Sn_IR_TIMEOUT | Sn_IR_RECV | Sn_IR_DISCON | Sn_IR_CON );// 开启主要中断源 - IMR_CONFLICT是IP地址冲突异常中断,IMR_UNREACH是UDP通信时,地址无法到达的异常中断setIMR( IM_IR7 | IM_IR6 );// Socket中断使能 - 使能中断触发[ 为1代表使能 ]setSIMR( 0x01 );// 使能socket 0 -> 0000 0001参考:// Write_W5500_1Byte(IMR,IM_IR7 | IM_IR6);// Write_W5500_1Byte(SIMR,S0_IMR);// Write_W5500_SOCK_1Byte(0, Sn_IMR, IMR_SENDOK | IMR_TIMEOUT | IMR_RECV | IMR_DISCON | IMR_CON);// IINCHIP_WRITE(Sn_MR(7), 0x20);//TCP模式下关闭无延时ACK// IINCHIP_WRITE(Sn_IMR(7), 0x0F);// IINCHIP_WRITE(IMR, 0xF0);// IINCHIP_WRITE(SIMR, 0xFE);
3.高阶开发【只看这个】
1. 配置并打开Socket sn——实现特定网络协议
Ethernet | Mbed
1.1. 打开socket
/* 创建一个socket:Socket号, TCP/UDP类型, 端口号, socket flags - 见SF_XXXXXXX */// SF_TCP_NODELAY 指定socket在收到对方的数据包后应该没有延时尽快答复ACK包,否则需要超时时间做延时。// SF_IO_NONBLOCK 用于控制socket.h中函数的行为,如启用这一选项,对这一socket调用socket.h中大部分函数不会阻塞等待调用结果,而是会在确认发出指令后尽快返回。if (socket(Temp_Socket->Sn, Sn_MR_TCP, Temp_Socket->Port, 0) == Temp_Socket->Sn) 或者if (socket(Temp_Socket->Sn, Sn_MR_TCP, Temp_Socket->port, SF_TCP_NODELAY | SF_IO_NONBLOCK) == Temp_Socket->Sn) // TCP-socket打开成功
1.2. 配置socket工作模式 - socket配置/信息获取
/* 设置socket的工作模式 - 阻塞/非阻塞 */ // SOCK_IO_BLOCK阻塞模式:在这种模式下,当一个操作(如读取或写入数据)不能立即完成时,程序会被阻塞,直到操作完成为止。这意味着程序需要等待操作完成才能继续执行后续代码 // SOCK_IO_NONBLOCK-非阻塞模式:在这种模式下,当一个操作不能立即完成时,程序不会被阻塞,而是立即返回一个错误,通常是EAGAIN或EWOULDBLOCK。这样,程序可以继续执行其他任务,而不必等待当前操作完成。int8_t socket_io_mode = SOCK_IO_BLOCK;ctlsocket(Temp_Socket->Sn, CS_SET_IOMODE, &socket_io_mode); //set blocking IO mode
1.3. socket 打开为某一网络协议
- TCP
/* TCP服务器开启-监听端口 */if (listen(Temp_Socket->Sn) == SOCK_OK) // TCP服务器打开成功/监听端口成功
- UDP
- 详细看实际工程
2. 基于网络协议的开发:
网络协议+对应API
2.1. 基本功能:【TCP重连/断连】
STM32+W5500+以太网应用开发+002_TCP 服务器和客户端_w5500 tcp客户端-CSDN博客
- 基本的收发
while (1){ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ printf(\"\\r\\nInitializing server socket\\r\\n\"); //Parameters in order socket_id, protocol TCP or UDP, Port number, Flags=0 //Return value is socket ID on success if(socket(1,Sn_MR_TCP,LISTEN_PORT,0)!=1)//创建一个socket:Socket号,TCP/UDP类型,端口号 { //error printf(\"Cannot create Socket!\\r\\n\"); while(1);//halt here } //success printf(\"Socket Created Successfully ! \\r\\n\"); uint8_t socket_io_mode=SOCK_IO_BLOCK; ctlsocket(1, CS_SET_IOMODE , &socket_io_mode);//set blocking IO mode printf(\"IP Address is %d.%d.%d.%d\\r\\n\",gWIZNETINFO.ip[0],gWIZNETINFO.ip[1],gWIZNETINFO.ip[2],gWIZNETINFO.ip[3]); printf(\"Start listening on port %d ! \\r\\n\",LISTEN_PORT); printf(\"Waiting for a client connection. \\r\\n\"); //Make it a passive socket (i.e. listen for connection) if(listen(1)!=SOCK_OK)//监听端口 {//our socket id is 1 (w5500 have 8 sockets from 0-7) //error printf(\"Cannot listen on port %d\",LISTEN_PORT); while(1); } uint8_t sr=0x00;//socket status register do { sr=getSn_SR(1);//获取Sn_SR寄存器,参数0~7 }while (sr!=SOCK_ESTABLISHED && sr!=SOCK_CLOSED); if(sr==SOCK_CLOSED) { printf(\"Some error occurred on server socket. Please restart.\\r\\n\"); while(1); } if(sr==SOCK_ESTABLISHED)//成功连接 { //we come here only when a client has connected. //Now we can read data from the socket printf(\"A client connected!\\r\\n\"); printf(\"Waiting for Client Data ...!\\r\\n\"); while(1) { int len=recv(1, receive_buff, RECEIVE_BUFF_SIZE);//从连接设备读取数据到receive_buff if(len==SOCKERR_SOCKSTATUS) { //client has disconnected printf(\"Client has disconnected\\r\\n\"); printf(\"*** SESSION OVER ***\\r\\n\\r\\n\"); break; } receive_buff[len]=\'\\0\'; printf(\"Received %d bytes from client\\r\\n\",len); printf(\"Data Received: %s\", receive_buff); if(strcmp((char*)receive_buff,\"Who are u\")==0) {//判断接收到\"Who are u\" memcpy(receive_buff,\"I am role_2099!\",15);//修改应答内容 len = 15; } //Echo the data back encloused in a [] pair send(1,(uint8_t*)\"[\",1);//starting sq bracket 向客户端发送[ send(1,receive_buff,len);// the data 向客户端发送接收到的内容或者特定的回答 send(1,(uint8_t*)\"]\",1);//closing sq bracket 向客户端发送] printf(\"\\r\\nECHO sent back to client\\r\\n\"); //Look for quit message and quit if received if(strcmp((char*)receive_buff,\"QUIT\")==0) {//收到\"QUIT\",端口客户端连接 printf(\"Received QUIT command from client\\r\\n\"); printf(\"Disconnecting ... \\r\\n\"); printf(\"*** SESSION OVER ***\\r\\n\\r\\n\"); disconnect(1);//disconnect from the clinet 断开客户端连接 break;//come out of while loop 退出,回到131行,重新等待客户端连接 } }//While loop (as long as client is connected) }//if block, client connect success } /* USER CODE END 3 */
- 客户端断连,服务器端断开连接
// 接收监听端口的消息,并返回接收字符长度Main_Socket_data.Recv_len = recv(Main_Socket_data.sn, Main_Socket_data.Recv_data_buf, MAX_RX_STR); //从连接设备读取数据到receive_buff// 客户端断连 - client has disconnected /*len为-7时,表示客户端断连1.用户主动断开2.用户发送了指定的退出命令*/if(Main_Socket_data.Recv_len == SOCKERR_SOCKSTATUS){ W5500_LOG(\"Client has disconnected\\r\\n\"); W5500_LOG(\"*** SESSION OVER ***\\r\\n\\r\\n\"); break;}// 客户端发送指定的\"退出\"命令if(strcmp((char*)Main_Socket_data.Recv_data_buf, \"QUIT\") == 0){ //收到\"QUIT\",端口客户端断连 disconnect(Main_Socket_data.sn); // 断开客户端连接 break; // 退出 -> 重新等待客户端连接}
- 断连后重新建立服务器
2.2. 工作程序:
这里要知道,本质是通过W5500中的各个寄存器,执行操作
我们使用的get_SR/get函数是直接封装了读写寄存器某个地址的操作的
非中断方式:基于获取Sn_SR寄存器值,执行操作
2.2.1. 用socket阻塞模式工作
2024-9-25
- 记得理解阻塞的概念
2.2.2. 用socket非阻塞模式工作
- 判断BUSK状态
2.2.3. 用中断架构的方式工作 - wizchip_getinterrupt();/getSn_IR(Main_Socket_data.Sn);【一定要初始化好中断】
中断函数/程序编写:
要注意的是,如果使用中断且用了官方的IO库,要避开SENDOK和TIMEOUT中断,这两个中断被IO库内部使用,如果自己清零的话会导致库的运行不正常。
另外,中断标志位一个常用的技巧是用来判断接收到了数据以及新建立了tcp链接:
有两种方式:
- 第一种:读写寄存器方式
uint8_t IR_REG_val; // 中断寄存器 - 判断中断的状态 - 主要是判断W5500的中断类型(是否为硬件/协议出错) == 硬件/协议/socket中断uint8_t SIR_REG_val; // Socket中断寄存器 - 判断Socket的中断状态 - 主要是判断哪个Socket触发中断了 == snuint8_t Sn_IR_REG_val; // Socket n中断寄存器 - 提供Socket n中断类型信息 - 如建立、中止、发送、接收、超时 == 中断类型 IntDispose: // i = Read_W5500_1Byte(IR);//读取中断标志寄存器 // Write_W5500_1Byte(IR, (i&0xf0));//回写清除中断标志 // 读取IR寄存器的值 - 读取中断标志寄存器 /* 注意:是否需要回写清除中断标志吗? 是否得到的就是不需要处理的IR寄存器值? */ IR_REG_val = getIR(); // if((i & CONFLICT) == CONFLICT)//IP地址冲突异常处理 if((IR_REG_val & IR_CONFLICT) == IR_CONFLICT)//IP地址冲突异常处理 { //自己添加代码 } // if((i & UNREACH) == UNREACH)//UDP模式下地址无法到达异常处理 if((IR_REG_val & IR_UNREACH) == IR_UNREACH)//UDP模式下地址无法到达异常处理 { //自己添加代码 } // i = Read_W5500_1Byte(SIR);//读取端口中断标志寄存器 // 读取SIR寄存器的值 - 读取端口中断标志寄存器 SIR_REG_val = getSIR(); // if((i & S0_INT) == S0_INT)//Socket0事件处理 if((i & S0_INT) == S0_INT)//Socket0事件处理 { getSn_IR(SIR_REG_val); j = Read_W5500_SOCK_1Byte(0,Sn_IR);//读取Socket0中断标志寄存器 Write_W5500_SOCK_1Byte(0,Sn_IR,j); if(j&IR_CON)//在TCP模式下,Socket0成功连接 { S0_State|=S_CONN;//网络连接状态0x02,端口完成连接,可以正常传输数据 } if(j&IR_DISCON)//在TCP模式下Socket断开连接处理 { Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);//关闭端口,等待重新打开连接 Socket_Init(0);//指定Socket(0~7)初始化,初始化端口0 S0_State=0;//网络连接状态0x00,端口连接失败 } if(j&IR_SEND_OK)//Socket0数据发送完成,可以再次启动S_tx_process()函数发送数据 { S0_Data|=S_TRANSMITOK;//端口发送一个数据包完成 } if(j&IR_RECV)//Socket接收到数据,可以启动S_rx_process()函数 { S0_Data|=S_RECEIVE;//端口接收到一个数据包 } if(j&IR_TIMEOUT)//Socket连接或数据传输超时处理 { Write_W5500_SOCK_1Byte(0,Sn_CR,CLOSE);// 关闭端口,等待重新打开连接 S0_State=0;//网络连接状态0x00,端口连接失败 } } if(SIR_REG_val = getSIR()) goto IntDispose;
- 第二种:利用封装的函数
Ethernet | Mbed
---- 1、获取中断源 - 封装了1.getIR();2.getSIR() ----typedef enum{ IK_WOL 通过接收魔术数据包唤醒 Lan。在 W500 中有效。 IK_PPPOE_TERMINATED PPPoE 已断开连接。 IK_DEST_UNREACH 目标IP和端口不可用,在W5200中无法使用。 IK_IP_CONFLICT 发生 IP 冲突。 IK_SOCK_0 套接字 0 中断。 IK_SOCK_1 套接字 1 中断。 IK_SOCK_2 套接字 2 中断。 IK_SOCK_3 套接字 3 中断。 IK_SOCK_4 套接字 4 中断,在 5100 中不可用。 IK_SOCK_5 Socket 5 中断,在 5100 中不可用。 IK_SOCK_6 Socket 6 中断,在 5100 中不可用。 IK_SOCK_7 Socket 7 中断,在 5100 中没有用。 IK_SOCK_ALL 所有 Socket interrpt.}intr_kind;清除 WIZCHIP 的中断。void wizchip_clrinterrupt (intr_kind intr); 获取 WIZCHIP 的中断。intr_kind wizchip_getinterrupt (void);---- 2、获取中断源的详细中断类型 >> Socket中断类型 - get Sn_IR/Sn_IMR ---- 这里不用sockint_kind枚举值判断用寄存器值(宏定义)即可/* Sn_IR寄存器的值 - Socket n中断寄存器*/// 5-7位保留发送完成中断#define Sn_IR_SENDOK 0x10超时中断#define Sn_IR_TIMEOUT 0x08接收到对方数据中断#define Sn_IR_RECV 0x04(当收到对方的断开申请)断开连接中断#define Sn_IR_DISCON 0x02与对方成功建立连接中断#define Sn_IR_CON 0x01ret = getSn_IR() -------------------------------------------------------------Sn_IR// 所涉及的寄存器setSn_IR(), getSn_IR()// 读取/设置Socket sn中断寄存器
有问题的版本,没修复【send发送有问题,只能发送一次,后面都BUSY】
void W5500_Interrupt_Task(void *argument){ /* 任务 - 变量 */ /* 获取的W5500中断源 [ 需要保存中断来源 - 用于清除中断 ] */ static intr_kind W5500_InterruptHandle; // intr_kind - (硬件/协议/socket sn中断) /* 任务 - 程序 */ while(1) { /* 等待事件组: bit0 - 事件0 - W5500中断 */ if(!W5500_InterruptFlag) { xEventGroupWaitBits(g_xEventInterrupt, W5500InterruptEvent, pdTRUE, pdTRUE, portMAX_DELAY); // 等待完成后清除事件 } W5500_InterruptFlag = 0; // 获取W5500的中断源 /* 功能:读取IR和SIR寄存器的值,分析得到中断源 */ // ctlwizchip(CW_GET_INTERRUPT, (void*)W5500_InterruptHandle);// 封装API W5500_InterruptHandle = wizchip_getinterrupt(); // 中底层API //真正的底层是寄存器读取(get set) /* 成功获得中断 - 开始处理 */ W5500_LOG(\"\\r\\nGet W5500Interrupt success.\\r\\n\");/-----------------------------------if版本 #if 0 /* 根据中断源执行对应中断操作 */ // 通过接收魔术数据包唤醒 Lan if (W5500_InterruptHandle & IK_WOL) { W5500_LOG(\"\\r\\nW5500 Error: IK_WOL...\\r\\n\"); } // PPPoE 已断开连接 if (W5500_InterruptHandle & IK_PPPOE_TERMINATED) { W5500_LOG(\"\\r\\nW5500 Error: IK_PPPOE_TERMINATED...\\r\\n\"); } // 目标IP和端口不可用 if (W5500_InterruptHandle & IK_DEST_UNREACH) { W5500_LOG(\"\\r\\nW5500 Error: IP or Port use fail!\\r\\nplease change IP or Port...\\r\\n\"); } // 发生 IP 冲突 if (W5500_InterruptHandle & IK_IP_CONFLICT) { W5500_LOG(\"\\r\\nW5500 Error: IP conflict!\\r\\nplease change IP...\\r\\n\"); } // 套接字 0 中断 - Main_Socket_data - TCP服务器 if (W5500_InterruptHandle & IK_SOCK_0) { /* 获取Socket sn中断类型 - 读取Sn_IR(sn)寄存器的值 */ Main_Socket_data.Socket_InterruptType = getSn_IR(Main_Socket_data.Sn); // 在TCP模式下,与对方成功建立连接中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_CON) { // setSn_IR(Main_Socket_data.Sn, Sn_IR_CON); W5500_LOG(\"\\r\\nW5500 - Socket 0 - TCP Server Connected.\\r\\n\"); // 当前socket成功连接/监听某端口 /* 开始监听... */ } // 在TCP模式下,(当收到对方的断开申请)断开连接中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_DISCON) { // setSn_IR(Main_Socket_data.Sn, Sn_IR_DISCON); W5500_LOG(\"\\r\\nW5500 - Socket 0 - TCP Server closed.\\r\\n\"); // 当前socket被关闭 W5500_LOG(\"Open the socket again.\\r\\n\"); // 尝试重新打开socket,并配置为TCP服务器(开始监听) /* 重新打开socket */ while(1) { /* 断开前一个客户端后,重新打开TcpSocket,并监听端口(开启TCP服务器) */ if (socket(Main_Socket_data.Sn, Sn_MR_TCP, Main_Socket_data.Port, 0) == Main_Socket_data.Sn) // TCP-socket打开成功 { /* 设置socket的工作模式为阻塞模式 */ int8_t socket_io_mode = SOCK_IO_BLOCK; ctlsocket(Main_Socket_data.Sn, CS_SET_IOMODE, &socket_io_mode); //set blocking IO mode W5500_LOG(\"Create Socket %d success.\\r\\n\", Main_Socket_data.Sn); // 重新开始监听端口 if(listen(Main_Socket_data.Sn) != SOCK_OK) { // 监听失败 W5500_LOG(\"listen on port %d fail.\", Main_Socket_data.Port); } else // 监听成功 - 创建TCP服务器成功 { W5500_LOG(\"Create Server success.\\r\\n\"); W5500_LOG(\"IP Address is %d.%d.%d.%d\\r\\n\",gWIZNETINFO.ip[0],gWIZNETINFO.ip[1],gWIZNETINFO.ip[2],gWIZNETINFO.ip[3]); W5500_LOG(\"Start listening on port %d\\r\\n\", Main_Socket_data.Port); W5500_LOG(\"Waiting for a client connection. \\r\\n\"); // 等待客户端连接 break; } } else // TCP-socket打开失败 { W5500_LOG(\"Create Socket fail. Try Create again.\\r\\n\"); } } } // 接收到客户端数据中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_RECV) { // setSn_IR(Main_Socket_data.Sn, Sn_IR_RECV); /* 成功获得Socket接收数据中断 */ W5500_LOG(\"\\r\\nGet Socket RecvInterrupt success.\\r\\n\"); /* 接收处理程序 */ Main_Socket_data.Recv_len = recv(Main_Socket_data.Sn, Main_Socket_data.Recv_data_buf, W5500_MAX_RX_STR);//从连接设备读取数据到receive_buff // 主动断连后recv接收为SOCKERR_SOCKSTATUS if(Main_Socket_data.Recv_len == SOCKERR_SOCKSTATUS) { //client has disconnected W5500_LOG(\"Client has disconnected\\r\\n\"); W5500_LOG(\"*** SESSION OVER ***\\r\\n\\r\\n\"); } W5500_LOG(\"Received %d bytes from client\\r\\n\",Main_Socket_data.Recv_len); W5500_LOG(\"Data Received: %s\", Main_Socket_data.Recv_data_buf); W5500_LOG(\"\\r\\n\"); // 发送指定字符串,回波指定数据 if(strcmp((char*)Main_Socket_data.Recv_data_buf, \"Who are u\") == 0) { //判断接收到\"Who are u\" memcpy(Main_Socket_data.Recv_data_buf,\"I am role_2099!\",15);//修改应答内容 Main_Socket_data.Recv_len = 15; } // 回传数据并分析返回值 SocketSendRet(send(Main_Socket_data.Sn, (uint8_t*)\"[\", 1));//starting sq bracket 向客户端发送[ SocketSendRet(send(Main_Socket_data.Sn, Main_Socket_data.Recv_data_buf, Main_Socket_data.Recv_len));// the data 向客户端发送接收到的内容或者特定的回答 SocketSendRet(send(Main_Socket_data.Sn, (uint8_t*)\"]\", 1));//closing sq bracket 向客户端发送] W5500_LOG(\"ECHO sent back to client.\\r\\n\"); }// 如果使用中断且用了官方的IO库:// 要避开SENDOK和TIMEOUT中断,这两个中断被IO库内部使用,如果自己清零的话会导致库的运行不正常。 // Socket连接或数据传输超时处理 if (Main_Socket_data.Socket_InterruptType & Sn_IR_TIMEOUT) {// setSn_IR(Main_Socket_data.Sn, Sn_IR_TIMEOUT); W5500_LOG(\"\\r\\nW5500 Error: Socket TIMEOUT.\\r\\n\"); } // 发送完成中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_SENDOK) {// setSn_IR(Main_Socket_data.Sn, Sn_IR_SENDOK); W5500_LOG(\"\\r\\nW5500 Socket %d SENDOK.\\r\\n\", Main_Socket_data.Sn); } }#if 0 // 套接字 sn 中断 - xxx_Socket_data - xxx if (W5500_InterruptHandle & IK_SOCK_Sn) { } // 某个套接字 sn 中断 - xxx_Socket_data - xxx if (W5500_InterruptHandle & IK_SOCK_ALL) { setSn_IR(Main_Socket_data.Sn, IK_SOCK_ALL); W5500_LOG(\"\\r\\nW5500 Socket ALL Interrupt.\\r\\n\"); }#endif#endif /-----------------------------------switch版本 switch (W5500_InterruptHandle) { case IK_SOCK_0: /* 获取Socket sn中断类型 - 读取Sn_IR(sn)寄存器的值 */ Main_Socket_data.Socket_InterruptType = getSn_IR(Main_Socket_data.Sn); // 在TCP模式下,与对方成功建立连接中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_CON) {/* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ wizchip_clrinterrupt(W5500_InterruptHandle); // setSn_IR(Main_Socket_data.Sn, Sn_IR_CON); W5500_LOG(\"\\r\\nW5500 - Socket 0 - TCP Server Connected.\\r\\n\"); // 当前socket成功连接/监听某端口 /* 开始监听... */ } // 在TCP模式下,(当收到对方的断开申请)断开连接中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_DISCON) {/* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ wizchip_clrinterrupt(W5500_InterruptHandle); // setSn_IR(Main_Socket_data.Sn, Sn_IR_DISCON); W5500_LOG(\"\\r\\nW5500 - Socket 0 - TCP Server closed.\\r\\n\"); // 当前socket被关闭 W5500_LOG(\"Open the socket again.\\r\\n\"); // 尝试重新打开socket,并配置为TCP服务器(开始监听) /* 重新打开socket */ while(1) { /* 断开前一个客户端后,重新打开TcpSocket,并监听端口(开启TCP服务器) */ if (socket(Main_Socket_data.Sn, Sn_MR_TCP, Main_Socket_data.Port, 0) == Main_Socket_data.Sn) // TCP-socket打开成功 { /* 设置socket的工作模式为阻塞模式 */ int8_t socket_io_mode = SOCK_IO_BLOCK; ctlsocket(Main_Socket_data.Sn, CS_SET_IOMODE, &socket_io_mode); //set blocking IO mode W5500_LOG(\"Create Socket %d success.\\r\\n\", Main_Socket_data.Sn); // 重新开始监听端口 if(listen(Main_Socket_data.Sn) != SOCK_OK) { // 监听失败 W5500_LOG(\"listen on port %d fail.\", Main_Socket_data.Port); } else // 监听成功 - 创建TCP服务器成功 { W5500_LOG(\"Create Server success.\\r\\n\"); W5500_LOG(\"IP Address is %d.%d.%d.%d\\r\\n\",gWIZNETINFO.ip[0],gWIZNETINFO.ip[1],gWIZNETINFO.ip[2],gWIZNETINFO.ip[3]); W5500_LOG(\"Start listening on port %d\\r\\n\", Main_Socket_data.Port); W5500_LOG(\"Waiting for a client connection. \\r\\n\"); // 等待客户端连接 break; } } else // TCP-socket打开失败 { W5500_LOG(\"Create Socket fail. Try Create again.\\r\\n\"); } } } // 接收到客户端数据中断 if (Main_Socket_data.Socket_InterruptType & Sn_IR_RECV) {/* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ wizchip_clrinterrupt(W5500_InterruptHandle); setSn_IR(Main_Socket_data.Sn, Sn_IR_SENDOK); /* 成功获得Socket接收数据中断 */ W5500_LOG(\"\\r\\nGet Socket RecvInterrupt success.\\r\\n\"); /* 接收处理程序 */ Main_Socket_data.Recv_len = recv(Main_Socket_data.Sn, Main_Socket_data.Recv_data_buf, W5500_MAX_RX_STR);//从连接设备读取数据到receive_buff // 主动断连后recv接收为SOCKERR_SOCKSTATUS if(Main_Socket_data.Recv_len == SOCKERR_SOCKSTATUS) { //client has disconnected W5500_LOG(\"Client has disconnected\\r\\n\"); W5500_LOG(\"*** SESSION OVER ***\\r\\n\\r\\n\"); } W5500_LOG(\"Received %d bytes from client\\r\\n\",Main_Socket_data.Recv_len); W5500_LOG(\"Data Received: %s\", Main_Socket_data.Recv_data_buf); W5500_LOG(\"\\r\\n\"); // 发送指定字符串,回波指定数据 if(strcmp((char*)Main_Socket_data.Recv_data_buf, \"Who are u\") == 0) { //判断接收到\"Who are u\" memcpy(Main_Socket_data.Recv_data_buf,\"I am role_2099!\",15);//修改应答内容 Main_Socket_data.Recv_len = 15; } // 回传数据并分析返回值 SocketSendRet(send(Main_Socket_data.Sn, (uint8_t*)\"[\", 1));//starting sq bracket 向客户端发送[ SocketSendRet(send(Main_Socket_data.Sn, Main_Socket_data.Recv_data_buf, Main_Socket_data.Recv_len));// the data 向客户端发送接收到的内容或者特定的回答 SocketSendRet(send(Main_Socket_data.Sn, (uint8_t*)\"]\", 1));//closing sq bracket 向客户端发送] W5500_LOG(\"ECHO sent back to client.\\r\\n\"); } break; default:/* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ //wizchip_clrinterrupt(W5500_InterruptHandle); break; } /* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ // wizchip_clrinterrupt(W5500_InterruptHandle); }}
3. 注意:无论是哪种开发方式,本质都是对寄存器状态查询,设置!!!(所以开发时,状态可能是并发的,而不是唯一的)
致命问题:寄存器是8位的如果我用简单的switch,那我一次只能处理一个判断,但是有时候中断的一起触发的,所以要用多个if!,不能用switch
正确:
错误:
开发问题:
W5500问题集锦(二)_w5500 spi连不上-CSDN博客
w5500常见问题及解决方案_w5500接收不到数据-CSDN博客
1. 端口打开有问题:
错误:char port
正确:uint16_t port
类型给错了
2. TCP服务器-只能连接一次/退出重连问题:
问题:调用一次listen只能监听一次
解决:
2.1. 第一种 利用通信告知服务器,客户端断连:
- 读取socket状态机(连接成功后)
- 如果客户端断开连接(断开前主动发出信息),我们要主动断开TCP链接,disconnect(关闭链接,关闭socket)
- 如果要重新开启服务器:重新打开TCP socket并监听该端口
while(1){ //服务器-监听端口 if(listen(sn)!=SOCK_OK) { //error printf(\"Cannot listen on port %d\",LISTEN_PORT); while(1); } //读取套接字状态机,根据状态机状态做对应工作 do { sr=getSn_SR(sn);//获取Sn_SR寄存器,参数0~7 } while (sr!=SOCK_ESTABLISHED && sr!=SOCK_CLOSED); //socket处于关闭状态,资源被释放。 //disconnect或close命令生效后,或者超时后,无视之前状态变为这个状态此时无法通信 if(sr == SOCK_CLOSED) { //请重新启动 printf(\"Some error occurred on server socket. Please restart.\\r\\n\"); while(1); } //成功连接 if(sr == SOCK_ESTABLISHED) { //... while(1)//开始工作 { //如果接收到退出信息,就断开连接,重新监听该端口 if(strcmp((char*)receive_buff,\"QUIT\")==0) { //收到\"QUIT\",端口客户端断开连接 disconnect(sn);//disconnect from the clinet 断开客户端连接 break;//come out of while loop 退出,回到131行,重新等待客户端连接 } } }}
2.2. 第二种 利用心跳包检测,检测客户端有没有断连【优先选择】
- 如果断连,会自动执行disconnect(关闭链接,关闭socket)
- 如果要重新开启服务器:重新打开TCP socket并监听该端口
TCP链接 - 自动心跳包检测(5)对于TCP链接,如果意外断连,比如网线被拔了呀之类的,W5500很可能并没法知道实际已经没有链接了,然后就认为自己还连着,一直等待着数据,然后就很爆炸。为此,需要加入心跳检测来保证确实通讯还正常。● 开启自动心跳检测,在socket初始化为TCP模式后:setSn_KPALVTR(sn,2); // 设置心跳包自动发送间隔,单位时间为5s,所以这里设置为10s。为0则不启用。以上代码等价于:uint8_t t = 2;setsockopt(sn, SO_KEEPALIVEAUTO, (void*)&t);需要注意的是,KeepAlive包会在socket状态变为SOCK_ESTABLISHED且与对方至少进行过一次收或发的通讯后进行传输。所以建立链接后建议立即随便发点什么。● 当然,也可以手动发送心跳包(没开启自动发送才行——第三个参数(发生间隔)给0):setsockopt(sn, SO_KEEPALIVESEND, (void *)0);
3. 关于阻塞和非阻塞的Socket工作模式
影响所有W5500 API
建议使用改为:
- 阻塞模式
这个模式下,如TCP接收数据,如果没接收到就会阻塞在这rev函数
int32_t recv(uint8_t sn, uint8_t * buf, uint16_t len);在阻塞io(默认)模式下,如果暂时没有数据,就会不停阻塞等待直到收到任意数据或者链接断开。在非阻塞io(即指定了SF_IO_NONBLOCK)模式下,如果暂时没有数据可接收,会立刻返回SOCK_BUSY
- 非阻塞 + 判断 SOCK_BUSY返回
感觉这个会更好些,可以去干别的事情,根据返回值
不会阻塞,不管成不成功,都有返回值(原来的成功/失败返回值 + 还没执行完SOCK_BUSY返回值)
如启用这一选项,对这一socket调用socket.h中大部分函数不会阻塞等待调用结果,而是会在确认发出指令后尽快返回。
要注意的是,启用后,大部分函数的返回值会为SOCK_BUSY【socket正忙】,这并不代表调用就失败了
4. 寄存器状态查询要用多个if,状态可能是并发的,不唯一
正确:
错误:
5. 如果使用中断且用了官方的IO库,要避开SENDOK和TIMEOUT中断,这两个中断被IO库内部使用,如果自己清零的话会导致库的运行不正常。
6. Sn_IR中断不触发
/* 清除W5500指定中断 - 依次清除该中断源的[IR,SIR,Sn_IR]所有中断 - 省去了set寄存器值 */ wizchip_clrinterrupt(W5500_InterruptHandle);
别用错地方了
7. 不同层的开发方式对比
最底层 - SPI读写寄存器IINCHIP_READ(Sn_IR(s));Sn_IR(s) - 返回寄存器xxx的地址中层 - 包装了|&操作setSn_IR();getIR();#define setSn_IR(sn, ir) WIZCHIP_WRITE(Sn_IR(sn), (ir & 0x1F))#define getSn_IR(sn) (WIZCHIP_READ(Sn_IR(sn)) & 0x1F)上层W5500_InterruptHandle = wizchip_getinterrupt(); wizchip_clrinterrupt(W5500_InterruptHandle);抽象层ctlwizchip(CW_GET_INTERRUPT, (void*)W5500_InterruptHandle);// 封装API