#include "w5500.h" #include "includes.h" #include "asm/spi.h" #include "generic/ioctl.h" //注意:作为服务器需要设置网关和IP在有效(路由器分配的IP)的同一个网段下,局域网客户端才能连接 //#error "err notece !!!"//务必注意:配置的网关和IP地址需要在路由器分配的IP同一段网络,否则无法通信 static const unsigned char gateway_ip[4] = {192, 168, 0, 1}; //网关参数,默认192.168.31.1 static const unsigned char ip_addr[4] = {192, 168, 0, 199}; //本机IP地址,默认192.168.31.199 static const unsigned char sub_mask[4] = {255, 255, 255, 0}; //子网掩码,默认255.255.255.0 static const unsigned char phy_addr[6] = {0x0c, 0x29, 0xab, 0x7c, 0x00, 0x01}; //网卡物理地址mac地址(第一个字节必须为偶数),默认0x0c29ab7c0001 #if SPI_USE_DEV static void *spi_handler = NULL; #else #define SPI_DLY_US 0 //使用IO模拟翻转时候的时钟跳变延时 #endif /******************************************************************************* * 函数名 : SPI_Configuration * 描述 : W5500 SPI初始化配置(STM32 SPI1) * 输入 : 无 * 输出 : 无 * 返回值 : 无 * 说明 : 无 *******************************************************************************/ static void *SPI_Configuration(void) { #if SPI_USE_DEV if (!spi_handler) { spi_handler = dev_open((unsigned char *)SPI_DEV_NAME, NULL); } W5500_SCLK_SET_HD(1); return &spi_handler; #else W5500_SCLK_SET_HD(1); return NULL; #endif } /******************************************************************** *函数名:W5500_io_init *功 能:W5500 IO 初始化 *参 数:interrupt_callback_func中断回调函数 *返回值:无 *********************************************************************/ void W5500_io_init(void (*interrupt_callback_func)(void)) { W5500_IO_INIT(); SPI_Configuration(); port_wakeup_reg(W5500_INT_EVENT, W5500_INT_IO, W5500_INT_EDGE, interrupt_callback_func); } /******************************************************************** *函数名:SPI_Read_Byte *功 能:SPI读取一个字节数据 *参 数:无 *返回值:读取的数据 *********************************************************************/ static unsigned char SPI_Read_Byte(void) { unsigned char i = 0; #if SPI_USE_DEV if (spi_handler) { dev_ioctl(spi_handler, IOCTL_SPI_READ_BYTE, &i); /*dev_read(spi_handler,&i,1);*/ } return i; #else unsigned char rByte = 0; W5500_SCLK_SET(0); w5500_delay_us(SPI_DLY_US); for (i = 0; i < 8; i++) { W5500_SCLK_SET(1); w5500_delay_us(SPI_DLY_US); rByte <<= 1; rByte |= W5500_MISO_GET(); w5500_delay_us(SPI_DLY_US); W5500_SCLK_SET(0); w5500_delay_us(SPI_DLY_US); } return rByte; #endif } /******************************************************************** *函数名:SPI_SendByte *功 能:SPI发送一个字节数据 *参 数:dt:待发送的数据 *返回值:无 *********************************************************************/ static void SPI_Send_Byte(unsigned char dt) { #if SPI_USE_DEV if (spi_handler) { dev_ioctl(spi_handler, IOCTL_SPI_SEND_BYTE, dt); /*dev_write(spi_handler,(unsigned char*)&dt,1);*/ } #else unsigned char i = 0; W5500_SCLK_SET(0); for (i = 0; i < 8; i++) { w5500_delay_us(SPI_DLY_US); W5500_SCLK_SET(0); w5500_delay_us(SPI_DLY_US); if ((dt << i) & 0x80) { W5500_MOSI_SET(1); } else { W5500_MOSI_SET(0); } w5500_delay_us(SPI_DLY_US); W5500_SCLK_SET(1); w5500_delay_us(SPI_DLY_US); } W5500_SCLK_SET(0); #endif } /******************************************************************** *函数名:SPI_Send_Short *功 能:SPI发送2个字节数据 *参 数:dt:待发送的数据 *返回值:无 *********************************************************************/ void SPI_Send_Short(unsigned short dt) { SPI_Send_Byte((unsigned char)(dt / 256)); //写数据高位 SPI_Send_Byte(dt); //写数据低位 } /******************************************************************** *函数名:Write_W5500_1Byte *功 能:W5500写寄存器 *参 数:reg寄存器,dat写的值 *返回值:无 *********************************************************************/ void Write_W5500_1Byte(unsigned short reg, unsigned char dat) { W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM1 | RWB_WRITE | COMMON_R); //通过SPI写控制字节,1个字节数据长度,写数据,选择通用寄存器 SPI_Send_Byte(dat);//写1个字节数据 W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Write_W5500_2Byte *功 能:通过SPI向指定地址寄存器写2个字节数据 *参 数:reg:16位寄存器地址,dat:16位待写入的数据(2个字节) *返回值:无 *********************************************************************/ void Write_W5500_2Byte(unsigned short reg, unsigned short dat) { W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM2 | RWB_WRITE | COMMON_R); //通过SPI写控制字节,2个字节数据长度,写数据,选择通用寄存器 SPI_Send_Short(dat);//写16位数据 W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Write_W5500_nByte *功 能:通过SPI向指定地址寄存器写n个字节数据 *参 数:reg:16位寄存器地址,*dat_ptr:待写入数据缓冲区指针, * size:待写入的数据长度 *返回值:无 *********************************************************************/ void Write_W5500_nByte(unsigned short reg, unsigned char *dat_ptr, unsigned short size) { unsigned short i; W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(VDM | RWB_WRITE | COMMON_R); //通过SPI写控制字节,N个字节数据长度,写数据,选择通用寄存器 for (i = 0; i < size; i++) { //循环将缓冲区的size个字节数据写入W5500 SPI_Send_Byte(*dat_ptr++);//写一个字节数据 } W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Write_W5500_SOCK_1Byte *功 能:通过SPI向指定端口寄存器写1个字节数据 *参 数:s:端口号,reg:16位寄存器地址,dat:待写入的数据 *返回值:无 *********************************************************************/ void Write_W5500_SOCK_1Byte(SOCKET s, unsigned short reg, unsigned char dat) { W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM1 | RWB_WRITE | (s * 0x20 + 0x08)); //通过SPI写控制字节,1个字节数据长度,写数据,选择端口s的寄存器 SPI_Send_Byte(dat);//写1个字节数据 W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Write_W5500_SOCK_2Byte *功 能:通过SPI向指定端口寄存器写2个字节数据 *参 数:s:端口号,reg:16位寄存器地址,dat:16位待写入的数据(2个字节) *返回值:无 *********************************************************************/ void Write_W5500_SOCK_2Byte(SOCKET s, unsigned short reg, unsigned short dat) { W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM2 | RWB_WRITE | (s * 0x20 + 0x08)); //通过SPI写控制字节,2个字节数据长度,写数据,选择端口s的寄存器 SPI_Send_Short(dat);//写16位数据 W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Write_W5500_SOCK_4Byte *功 能:通过SPI向指定端口寄存器写4个字节数据 *参 数:s:端口号,reg:16位寄存器地址,*dat_ptr:待写入的4个字节缓冲区指针 *返回值:无 *********************************************************************/ void Write_W5500_SOCK_4Byte(SOCKET s, unsigned short reg, unsigned char *dat_ptr) { W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM4 | RWB_WRITE | (s * 0x20 + 0x08)); //通过SPI写控制字节,4个字节数据长度,写数据,选择端口s的寄存器 SPI_Send_Byte(*dat_ptr++);//写第1个字节数据 SPI_Send_Byte(*dat_ptr++);//写第2个字节数据 SPI_Send_Byte(*dat_ptr++);//写第3个字节数据 SPI_Send_Byte(*dat_ptr++);//写第4个字节数据 W5500_CS_SET(1); //置W5500的SCS为高电平 } /******************************************************************** *函数名:Read_W5500_1Byte *功 能:读W5500指定地址寄存器的1个字节数据 *参 数:reg:16位寄存器地址 *返回值:读取到寄存器的1个字节数据 *********************************************************************/ unsigned char Read_W5500_1Byte(unsigned short reg) { unsigned char i; W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM1 | RWB_READ | COMMON_R); //通过SPI写控制字节,1个字节数据长度,读数据,选择通用寄存器 i = SPI_Read_Byte(); W5500_CS_SET(1);//置W5500的SCS为高电平 return i;//返回读取到的寄存器数据 } /******************************************************************** *函数名:Read_W5500_SOCK_1Byte *功 能:读W5500指定端口寄存器的1个字节数据 *参 数:s:端口号,reg:16位寄存器地址 *返回值:读取到寄存器的1个字节数据 *********************************************************************/ unsigned char Read_W5500_SOCK_1Byte(SOCKET s, unsigned short reg) { unsigned char i; W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM1 | RWB_READ | (s * 0x20 + 0x08)); //通过SPI写控制字节,1个字节数据长度,读数据,选择端口s的寄存器 i = SPI_Read_Byte(); W5500_CS_SET(1);//置W5500的SCS为高电平 return i;//返回读取到的寄存器数据 } /******************************************************************** *函数名:Read_W5500_SOCK_2Byte *功 能:读W5500指定端口寄存器的2个字节数据 *参 数:s:端口号,reg:16位寄存器地址 *返回值:读取到寄存器的2个字节数据(16位) *********************************************************************/ unsigned short Read_W5500_SOCK_2Byte(SOCKET s, unsigned short reg) { unsigned short i; W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(reg);//通过SPI写16位寄存器地址 SPI_Send_Byte(FDM2 | RWB_READ | (s * 0x20 + 0x08)); //通过SPI写控制字节,2个字节数据长度,读数据,选择端口s的寄存器 i = SPI_Read_Byte(); i *= 256; i += SPI_Read_Byte(); //读取低位数据 W5500_CS_SET(1);//置W5500的SCS为高电平 return i;//返回读取到的寄存器数据 } /******************************************************************** *函数名:Read_SOCK_Data_Buffer *功 能:从W5500接收数据缓冲区中读取数据 *参 数:s:端口号,*dat_ptr:数据保存缓冲区指针 *返回值:读取到的数据长度,rx_size个字节 *********************************************************************/ unsigned short Read_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr) { unsigned short rx_size; unsigned short offset, offset1; unsigned short i; unsigned char j; rx_size = Read_W5500_SOCK_2Byte(s, Sn_RX_RSR); if (rx_size == 0) { return 0; //没接收到数据则返回 } if (rx_size > SOCKET_MAX_SIZE) { rx_size = SOCKET_MAX_SIZE; } offset = Read_W5500_SOCK_2Byte(s, Sn_RX_RD); offset1 = offset; offset &= (S_RX_SIZE - 1); //计算实际的物理地址 W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(offset);//写16位地址 SPI_Send_Byte(VDM | RWB_READ | (s * 0x20 + 0x18)); //写控制字节,N个字节数据长度,读数据,选择端口s的寄存器 if ((offset + rx_size) < S_RX_SIZE) { //如果最大地址未超过W5500接收缓冲区寄存器的最大地址 for (i = 0; i < rx_size; i++) { //循环读取rx_size个字节数据 j = SPI_Read_Byte(); //读取1个字节数据 *dat_ptr = j; //将读取到的数据保存到数据保存缓冲区 dat_ptr++;//数据保存缓冲区指针地址自增1 } } else { //如果最大地址超过W5500接收缓冲区寄存器的最大地址 offset = S_RX_SIZE - offset; for (i = 0; i < offset; i++) { //循环读取出前offset个字节数据 j = SPI_Read_Byte(); //读取1个字节数据 *dat_ptr = j; //将读取到的数据保存到数据保存缓冲区 dat_ptr++;//数据保存缓冲区指针地址自增1 } W5500_CS_SET(1); //置W5500的SCS为高电平 W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(0x00);//写16位地址 SPI_Send_Byte(VDM | RWB_READ | (s * 0x20 + 0x18)); //写控制字节,N个字节数据长度,读数据,选择端口s的寄存器 for (; i < rx_size; i++) { //循环读取后rx_size-offset个字节数据 j = SPI_Read_Byte(); //读取1个字节数据 *dat_ptr = j; //将读取到的数据保存到数据保存缓冲区 dat_ptr++;//数据保存缓冲区指针地址自增1 } } W5500_CS_SET(1); //置W5500的SCS为高电平 offset1 += rx_size; //更新实际物理地址,即下次读取接收到的数据的起始地址 Write_W5500_SOCK_2Byte(s, Sn_RX_RD, offset1); Write_W5500_SOCK_1Byte(s, Sn_CR, RECV);//发送启动接收命令 return rx_size;//返回接收到数据的长度 } /******************************************************************** *函数名:Write_SOCK_Data_Buffer *功 能:将数据写入W5500的数据发送缓冲区 *参 数:s:端口号,*dat_ptr:数据保存缓冲区指针,size:待写入数据的长度 *返回值:无 *********************************************************************/ void Write_SOCK_Data_Buffer(SOCKET s, unsigned char *dat_ptr, unsigned short size) { unsigned short offset, offset1; unsigned short i = 0; offset = Read_W5500_SOCK_2Byte(s, Sn_TX_WR); offset1 = offset; offset &= (S_TX_SIZE - 1); //计算实际的物理地址 W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(offset);//写16位地址 SPI_Send_Byte(VDM | RWB_WRITE | (s * 0x20 + 0x10)); //写控制字节,N个字节数据长度,写数据,选择端口s的寄存器 if ((offset + size) < S_TX_SIZE) { //如果最大地址未超过W5500发送缓冲区寄存器的最大地址 for (i = 0; i < size; i++) { //循环写入size个字节数据 SPI_Send_Byte(*dat_ptr++);//写入一个字节的数据 } } else { //如果最大地址超过W5500发送缓冲区寄存器的最大地址 offset = S_TX_SIZE - offset; for (i = 0; i < offset; i++) { //循环写入前offset个字节数据 SPI_Send_Byte(*dat_ptr++);//写入一个字节的数据 } W5500_CS_SET(1); //置W5500的SCS为高电平 W5500_CS_SET(0);//置W5500的SCS为低电平 SPI_Send_Short(0x00);//写16位地址 SPI_Send_Byte(VDM | RWB_WRITE | (s * 0x20 + 0x10)); //写控制字节,N个字节数据长度,写数据,选择端口s的寄存器 for (; i < size; i++) { //循环写入size-offset个字节数据 SPI_Send_Byte(*dat_ptr++);//写入一个字节的数据 } } W5500_CS_SET(1); //置W5500的SCS为高电平 offset1 += size; //更新实际物理地址,即下次写待发送数据到发送数据缓冲区的起始地址 Write_W5500_SOCK_2Byte(s, Sn_TX_WR, offset1); Write_W5500_SOCK_1Byte(s, Sn_CR, SEND);//发送启动发送命令 } /******************************************************************** *函数名:W5500_Hardware_Reset *功 能:硬件复位W5500 *参 数:无 *返回值:无 *********************************************************************/ void W5500_Hardware_Reset(void) { u32 cnt = 0; W5500_RST_SET(0);//复位引脚拉低 w5500_delay_ms(200); W5500_RST_SET(1);//复位引脚拉高 w5500_delay_ms(200); while ((Read_W5500_1Byte(PHYCFGR)&LINK) == 0) { //等待以太网连接完成 cnt++; w5500_delay_ms(10); if (cnt > 1000) { w5500_log("%s %d err \n", __FUNCTION__, __LINE__); break; } } } /******************************************************************** *函数名:W5500_ip_addr_get *功 能:获取注册到W5500的IP地址 *参 数:无 *返回值:IP地址 *********************************************************************/ unsigned int W5500_ip_addr_get(void) { return ((ip_addr[0] << 24) | (ip_addr[1] << 16) | (ip_addr[2] << 8) | (ip_addr[3])); } /******************************************************************** *函数名:W5500_Init *功 能:初始化W5500寄存器 *参 数:无 *返回值:无 *********************************************************************/ void W5500_Init(void) { unsigned char i = 0; W5500_io_init(socket_interrupt);//初始化IO,注册socket中断函数 W5500_Hardware_Reset();//硬件复位W5500 Write_W5500_1Byte(MR, RST);//软件复位W5500,置1有效,复位后自动清0 w5500_delay_ms(10);//延时10ms,自己定义该函数 //设置网关(Gateway)的IP地址,Gateway_IP为4字节unsigned char数组,自己定义 //使用网关可以使通信突破子网的局限,通过网关可以访问到其它子网或进入Internet Write_W5500_nByte(GAR, (unsigned char *)gateway_ip, 4); //设置子网掩码(MASK)值,SUB_MASK为4字节unsigned char数组,自己定义 //子网掩码用于子网运算 Write_W5500_nByte(SUBR, (unsigned char *)sub_mask, 4); //设置物理地址,PHY_ADDR为6字节unsigned char数组,自己定义,用于唯一标识网络设备的物理地址值 //该地址值需要到IEEE申请,按照OUI的规定,前3个字节为厂商代码,后三个字节为产品序号 //如果自己定义物理地址,注意第一个字节必须为偶数 Write_W5500_nByte(SHAR, (unsigned char *)phy_addr, 6); //设置本机的IP地址,IP_ADDR为4字节unsigned char数组,自己定义 //注意,网关IP必须与本机IP属于同一个子网,否则本机将无法找到网关 Write_W5500_nByte(SIPR, (unsigned char *)ip_addr, 4); //设置发送缓冲区和接收缓冲区的大小,参考W5500数据手册 for (i = 0; i < 8; i++) { Write_W5500_SOCK_1Byte(i, Sn_RXBUF_SIZE, 0x02); //Socket Rx memory size=2k Write_W5500_SOCK_1Byte(i, Sn_TXBUF_SIZE, 0x02); //Socket Tx mempry size=2k } //设置重试时间,默认为2000(200ms) //每一单位数值为100微秒 Write_W5500_2Byte(RTR, 200 * 10); //设置重试次数,默认为255次 //如果重发的次数超过设定值,则产生超时中断(相关的端口中断寄存器中的Sn_IR 超时位(TIMEOUT)置“1”) Write_W5500_1Byte(RCR, 255); } /******************************************************************** *函数名:Socket_Connect *功 能:TCP模式socket连接 *参 数:s:端口号 *返回值:无 *********************************************************************/ unsigned char Socket_Connect(SOCKET s) { Write_W5500_SOCK_1Byte(s, Sn_MR, MR_TCP); //设置socket为TCP模式 Write_W5500_SOCK_1Byte(s, Sn_CR, OPEN); //打开Socket w5500_delay_ms(5);//延时5ms if (Read_W5500_SOCK_1Byte(s, Sn_SR) != SOCK_INIT) { //如果socket打开失败 Write_W5500_SOCK_1Byte(s, Sn_CR, CLOSE); //打开不成功,关闭Socket w5500_log("%s %d err \n", __FUNCTION__, __LINE__); return FALSE;//返回FALSE(0x00) } Write_W5500_SOCK_1Byte(s, Sn_CR, CONNECT); //设置Socket为Connect模式 return TRUE;//返回TRUE,设置成功 } /******************************************************************** *函数名:Socket_Listen *功 能:TCP模式设置指定Socket(0~7)作为服务器等待远程主机的连接 *参 数:s:端口号 *返回值:成功返回TRUE(0xFF),失败返回FALSE(0x00) *说 明:当本机Socket工作在服务器模式时,引用该程序,等等远程主机的连接 * 该程序只调用一次,就使W5500设置为服务器模式 *********************************************************************/ unsigned char Socket_Listen(SOCKET s) { Write_W5500_SOCK_1Byte(s, Sn_MR, MR_TCP); //设置socket为TCP模式 Write_W5500_SOCK_1Byte(s, Sn_CR, OPEN); //打开Socket w5500_delay_ms(5);//延时5ms if (Read_W5500_SOCK_1Byte(s, Sn_SR) != SOCK_INIT) { //如果socket打开失败 Write_W5500_SOCK_1Byte(s, Sn_CR, CLOSE); //打开不成功,关闭Socket w5500_log("%s %d err \n", __FUNCTION__, __LINE__); return FALSE;//返回FALSE(0x00) } Write_W5500_SOCK_1Byte(s, Sn_CR, LISTEN); //设置Socket为侦听模式 w5500_delay_ms(5);//延时5ms if (Read_W5500_SOCK_1Byte(s, Sn_SR) != SOCK_LISTEN) { //如果socket设置失败 Write_W5500_SOCK_1Byte(s, Sn_CR, CLOSE); //设置不成功,关闭Socket w5500_log("%s %d err \n", __FUNCTION__, __LINE__); return FALSE;//返回FALSE(0x00) } return TRUE; //至此完成了Socket的打开和设置侦听工作,至于远程客户端是否与它建立连接,则需要等待Socket中断, //以判断Socket的连接是否成功。参考W5500数据手册的Socket中断状态 //在服务器侦听模式不需要设置目的IP和目的端口号 } /******************************************************************** *函数名:Socket_UDP *功 能:设置指定Socket(0~7)为UDP模式 *参 数:s:端口号 *返回值:成功返回TRUE(0xFF),失败返回FALSE(0x00) *说 明:如果Socket工作在UDP模式,引用该程序,在UDP模式下,Socket通信不需 * 要建立连接,该程序只调用一次,就使W5500设置为UDP模式 *********************************************************************/ unsigned char Socket_UDP(SOCKET s) { Write_W5500_SOCK_1Byte(s, Sn_MR, MR_UDP); //设置Socket为UDP模式*/ Write_W5500_SOCK_1Byte(s, Sn_CR, OPEN); //打开Socket*/ w5500_delay_ms(5);//延时5ms if (Read_W5500_SOCK_1Byte(s, Sn_SR) != SOCK_UDP) { //如果Socket打开失败 Write_W5500_SOCK_1Byte(s, Sn_CR, CLOSE); //打开不成功,关闭Socket w5500_log("%s %d err \n", __FUNCTION__, __LINE__); return FALSE;//返回FALSE(0x00) } else { return TRUE; } //至此完成了Socket的打开和UDP模式设置,在这种模式下它不需要与远程主机建立连接 //因为Socket不需要建立连接,所以在发送数据前都可以设置目的主机IP和目的Socket的端口号 //如果目的主机IP和目的Socket的端口号是固定的,在运行过程中没有改变,那么也可以在这里设置 }