此次项目中涉及到W5500芯片的功能如下:
1.利用一个硬件Socket创建UDP作为与上位机通讯的接口
2.通过提供底层函数帮助上位机实现CreateUDP,UDPSend,UDPRecv的功能
在项目中所遇到的难点:
1.在前期不明白如何在初始化函数结束后,继续创建UDP端口,看了许多例程都没有这样的需求和代码实现。
2.数据发送会无法正常发送
初始化第一个UDP端口用于与上位机通讯
伪代码如下
void W5500_Udp_Init()
{
GPIO_Configuration();
W5500SpiInit(); //MCU与W5500之间通过SPI进行通讯,做初始化操作
W5500SwReset(); //硬件重启W5500芯片
Load_Net_Parameters(); //装载网络相关的参数
W5500_Initialization(); //W5500初始化配置
W5500_Socket_Set(); //端口硬件初始化配置
}
W5500SwReset()将W5500芯片进行复位操作
Load_Net_Parameters()函数中主要对以下参数进行赋值
1.网关参数 Wirte_W5500_nByte(GAR,Gateway_IP,4);
//将四个长度的Gateway_IP数组数据写入GAR通用寄存器中
2.子网掩码 Wirte_W5500_nByte(SUBR,Sub_Mask,4);
3.物理地址 Wirte_W5500_nByte(SHAR,Phy_Addr,6);
4.本机IP地址 Wirte_W5500_nByte(SIPR,IP_Addr,4);
5.端口0的端口号
注:以上数据都保存在Uint8_t类型的数组中
W5500_Initialization()函数将上述参数写入对应通用寄存器绿色操作部分
完成上述绿色字体操作后
设置发送缓冲区和接收缓冲区的大小 Sn_RXBUF_SIZE&&Sn_TXBUF_SIZE寄存器
设置重试时间 RTR寄存器
设置重设次数 RCR寄存器
至此就已经完成了W5500芯片的通用寄存器的初始化工作,下面就是对于具体Socket的初始化以及发送和接收。
下述函数展示了如何创建Udp端口以及实现接收到的数据就发送的功能。
void do_udp()
{
switch(getSn_SR(0)) //获取Socket0的状态
{
case SOCK_UDP: //如果当前已经是UDP模式,则实现以下功能
Delay_ms(100);
if(getSn_IR(0) & Sn_IR_RECV) //若该端口的Sn_IR寄存器中Recv寄存器被置位
{
setSn_IR(0, Sn_IR_RECV); //那么再将其手动置1
}
//数据回环测试程序,数据从上位机发送给W5500芯片,W5500接收到数据后再次发送给上位机
if((len=getSn_RX_RSR(0))>0)
{
memset(buffer,0,len+1);
recvfrom(0,buffer);
sendto(0,buffer,len, remote_ip, remote_port);
}
break;
case SOCK_CLOSED: //如果当前该端口处于关闭状态
socket(0,Sn_MR_UDP,local_port,0); //则将其初始化为端口号为local_port的UDP形式Socket硬件接口
break;
}
}
接收函数中需要注意的是:
UDPRecvBuf接收到的数组前8个数组分别是
1.UDPRecvBuf[0]-UDPRecvBuf[3] 为远端IP地址
2.UDPRecvBuf[4]-UDPRecvBuf[5] 为远端Port端口号
3.UDPRecvBuf[6]-UDPRecvBuf[7] 为数据长度
因此从第九个数组开始UDPRecvBuf[8]开始才是真正的数据部分。
发送数组中需要注意的是:
因为是UDP的形式,此时向不同的IP地址和端口号发送数据的时候不需要再次配置目标IP地址和端口号。但是需要将目标IP地址和端口号直接包含在数据包中!(来自gpt3.5的分析还需自行判断,我根据可实现代码来看还是需要对准备发送数据的Socket端口对应的Sn_DIPR寄存器写入目标IP地址,向准备发送数据的Socket端口对应的Sn_DPORT寄存器写入目标Port端口号)并且此时Socket已经成功初始化,那么数据就可以正常发送。
此处参考WIZnet官方例程
uint16 sendto(SOCKET s, const uint8 * buf, uint16 len, uint8 * addr, uint16 port)
{
.../*主要操作如下*/
IINCHIP_WRITE( Sn_DIPR0(s), addr[0]); //向对应reg中写入目标ip地址第一个字节
IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
IINCHIP_WRITE( Sn_DPORT0(s),(uint8)((port & 0xff00) >> 8)); //端口号
IINCHIP_WRITE( Sn_DPORT1(s),(uint8)(port & 0x00ff));
// copy data
send_data_processing(s, (uint8 *)buf, ret); //发送数据
IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND); //根据此标志位进行发送操作
...
}
void send_data_processing(SOCKET s, uint8 *data, uint16 len)
{
uint16 ptr =0;
uint32 addrbsb =0;
ptr = IINCHIP_READ( Sn_TX_WR0(s) );
ptr = ((ptr & 0x00ff) << 8) + IINCHIP_READ(Sn_TX_WR1(s));
addrbsb = (uint32)(ptr<<8) + (s<<5) + 0x10;
wiz_write_buf(addrbsb, data, len);
ptr += len;
IINCHIP_WRITE( Sn_TX_WR0(s) ,(uint8)((ptr & 0xff00) >> 8));
IINCHIP_WRITE( Sn_TX_WR1(s),(uint8)(ptr & 0x00ff));
}
uint16 wiz_write_buf(uint32 addrbsb,uint8* buf,uint16 len) // W5500将通过SPI获取的数据写入相关寄存器,并返回写入的数据长度
{
uint16 idx = 0; // idx定义为正在写入的第几个数
if(len == 0) printf("Unexpected2 length 0\r\n"); // 写入数据为空;len表示写入数据的长度
IINCHIP_CSoff(); // CS=0, SPI数据帧开始
IINCHIP_SpiSendData( (addrbsb & 0x00FF0000)>>16); // 地址段,提供16位偏移地址(0000 0000 0000 0000)
IINCHIP_SpiSendData( (addrbsb & 0x0000FF00)>> 8); // 控制段,共8位(0000 0000 高5位BSB位为00000表示通用寄存器)
IINCHIP_SpiSendData( (addrbsb & 0x000000F8) + 4); // 控制段+4(0000 0100 RWB位置1表示写入,OM位为00表示SPI工作模式为VDM)
for(idx = 0; idx < len; idx++) // 数据段,写入数据值
{
IINCHIP_SpiSendData(buf[idx]); // MCU通过SPI发送数据
}
IINCHIP_CSon(); // CS=1, SPI数据帧结束
return len; // 返回写入的数据长度值
}
后续继续初始化新的UDP端口,是可以的,不需要复位芯片,不需要重新配置通用寄存器内容。只需要给他一个正常范围内的Port端口号即可,具体函数实现可以参考do_udp()函数中所调用的socket()函数。
uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)
{
uint8 ret;
if (
((protocol&0x0F) == Sn_MR_TCP) || //判断Socket创建类型
((protocol&0x0F) == Sn_MR_UDP) ||
((protocol&0x0F) == Sn_MR_IPRAW) ||
((protocol&0x0F) == Sn_MR_MACRAW) ||
((protocol&0x0F) == Sn_MR_PPPOE)
)
{
close(s); //先关闭该Socket
IINCHIP_WRITE(Sn_MR(s) ,protocol | flag); //对该Socket对应的reg中写入协议类型
if (port != 0) {
IINCHIP_WRITE( Sn_PORT0(s) ,(uint8)((port & 0xff00) >> 8)); //写入端口号
IINCHIP_WRITE( Sn_PORT1(s) ,(uint8)(port & 0x00ff));
} else {
local_port++; // if don't set the source port, set local_port number.
IINCHIP_WRITE(Sn_PORT0(s) ,(uint8)((local_port & 0xff00) >> 8));
IINCHIP_WRITE(Sn_PORT1(s) ,(uint8)(local_port & 0x00ff));
}
IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_OPEN); // run sockinit Sn_CR
/* wait to process the command... */
while( IINCHIP_READ(Sn_CR(s)) )
;
/* ------- */
ret = 1;
}
else
{
ret = 0;
}
return ret;
}