工程源码下载:基于裸机和Freertos的W5500网络通信工程
目录
1. socket初始化UDP
1.1 打开socket并配置位UDP协议
1.2 初始化完成清理接收中断
4. UDP发送数据函数sendto
5. UDP接收数据函数recvfrom
6.【UDP发送】UDP广播数据
7.【UDP接收】UDP执行回调函数
7.1 获取链接对象的IP地址
7.2 协议数据类型建立
1. socket初始化UDP
这里介绍一下函数getsockopt函数,它是存在于socket.c中的。
函数原型如下:
int8_t getsockopt(uint8_t sn, sockopt_type sotype, void* arg)
sn socket号(0-7)
sotype 指向要发送的数据的缓冲区
arg 缓冲区中数据的字节长度
int8_t getsockopt(uint8_t sn, sockopt_type sotype, void* arg)
这是一个csae结构的函数,通过判断输入的参数来执行相应的函数。
我们可以利用这个函数获得socket的状态,这个函数返回的参数就是其相应的状态值。例如:
uint8_t state;getsockopt(sn, SO_STATUS, (void *)&state); //获取socket的状态
这个函数输入的SO_STATUS获取状态,根据上述函数直接指向函数:
case SO_STATUS:*(uint8_t*) arg = getSn_SR(sn);
break;
getSn_SR(sn)这个函数是直接读取其SR状态寄存器
返回的值参照说明书:
例如当前处于UDP模式下。
#define SOCK_UDP 0x22
#define SOCK_CLOSED 0x00
我们利用读取状态寄存器的值来做相应的判断;
鉴于此,我们写下UDP状态机函数:
uint8_t do_udp(uint8_t sn, uint16_t port)
{uint8_t state;getsockopt(sn, SO_STATUS, (void *)&state); //获取socket的状态switch (state){case SOCK_CLOSED: //socket处于关闭状态//socket(sn, Sn_MR_UDP, port, 0x00);//如果flags参数不需要的话直接传个0进去就行 if(socket(sn, Sn_MR_UDP, port, SF_IO_NONBLOCK) == sn){ //初始化socketprintf("set udp socket success");// 打开成功}else{printf("set udp socket success");// 打开失败}break; //socket关闭状态 case SOCK_UDP: //socket处于初始化完成(打开)状态HAL_Delay(10);if (getSn_IR(sn) & Sn_IR_RECV){setSn_IR(sn, Sn_IR_RECV); //Sn_IR的RECV位置1,清接收中断*}UDPCallback(sn); //回调函数中执行收发任务 break;}return state;
}
getSn_IR(sn)是获得接收中断寄存器,来判断是否是Sn_IR_RECV中断类型。
Sn_IR_RECV是获得接收中断寄存器值为:
#define Sn_IR_RECV 0x04
0x04对应第三位,表示只要接收到数据,此位生效。
也就是说通过获取中断寄存器的值来判断是否是自己想要的中断类型。
if (getSn_IR(sn) & Sn_IR_RECV){setSn_IR(sn, Sn_IR_RECV); //Sn_IR的RECV位置1,清接收中断*}
1.1 打开socket并配置位UDP协议
注:打开socket前先配置好网络参数。我们上一章已将将网络参数配置好了,链接如下:
上一章W5500网络参数配置
socket最初处于SOCK_CLOSED状态,这个时候是无法进行通讯的。为了进行通讯,需要先把socket打开并配置为某一协议,方法为调用socket.h中的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.
我们这里要实现的功能是,如果读到状态寄存器,发现socket关闭了。那么我们执行这个函数:
socket(sn, Sn_MR_UDP, port, SF_IO_NONBLOCK);
//socket(sn, Sn_MR_UDP, port, 0x00);//如果flags参数不需要的话直接传个0进去就行 if(socket(sn, Sn_MR_UDP, port, SF_IO_NONBLOCK) == sn){printf("set udp socket success");// 打开成功}else{printf("set udp socket success");// 打开失败}
Sn_MR_UDP = 0x02 是模式寄存器下的协议类型
#define SF_IO_NONBLOCK 0x01
1.2 初始化完成清理接收中断
case SOCK_UDP: //socket处于初始化完成(打开)状态HAL_Delay(10);if (getSn_IR(sn) & Sn_IR_RECV){setSn_IR(sn, Sn_IR_RECV); //Sn_IR的RECV位置1,清接收中断*}UDPCallback(sn); //回调函数中执行收发任务
该UDP状态机程序流程图如下:
直接的用法例如:
if(socket(2,Sn_MR_UDP,8008,SF_IO_NONBLOCK) == 2){// 打开成功
}else{// 打开失败
}
4. UDP发送数据函数sendto
描述: 发送UDP数据报给参数指定的IP地址
int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port);
sn socket号(0-7)
buf 指向要发送的数据的缓冲区
len 缓冲区中数据的字节长度
addr 目标IP地址,uint8_t[4]
port 目标端口号
举例:如使用初始化为udp的socket 1发送数据给192.168.1.100:8088:
char buf[] = "i love yinyin sun";
uint8_t remote_ip[4] = {192,168,1,100};
sendto(1,buf,strlen(buf),remote_ip,8088);
5. UDP接收数据函数recvfrom
接收时需要使用recvfrom函数。
接收UDP或MACRAW数据报
int32_t recvfrom(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port);
sn socket号(0-7)
buf 指向要接收数据的缓冲区
len 缓冲区的最大长度,当大于数据包大小时,接收数据包大小的数据;小于时,接收 len大小的数据。
addr 用于返回发送者ip地址,仅在对每个收的包第一次调用recv时有效。
port 用于返回发送者的端口号,仅在对每个收的包第一次调用recv时有效。
6.【UDP发送】UDP广播数据
广播时根据需求构造目标IP地址如果要全局广播,则发往255.255.255.255如果要在192.168.0.100(即掩码为255.255.255.0)上广播,则发往192.168.1.255。
void udp_broadcast(uint8_t sn, uint8_t *buf,uint16_t len, uint16_t port)
{uint8_t remote_ip[4] = {255, 255, 255, 255};sendto(sn, buf, len, remote_ip, port);
}
我们使用的时候就这样用:
udp_broadcast(0,(uint8_t *)"hello sunyinyin",sizeof("hello sunyinyin"),8088);
7.【UDP接收】UDP执行回调函数
7.1 获取链接对象的IP地址
UDP下,recvfrom中直接能获得对方的ip地址。但是对于TCP链接,主要是TCP服务器,如果需要知道链接对象的ip地址的话有两种写法。
uint8_t ip[4];
uint16_t port;
uint16_t local_port_udp;
getSn_DIPR(sn,ip);// 获得连接对象的ip
port = getSn_DPORT(sn);// 获得连接对象的端口号
//getsockopt(sn,SO_DESTIP,(void *)ip);// 获得连接对象的ip
//getsockopt(sn,SO_DESTPORT,(void *)&port);// 获得连接对象的端口号
7.2 协议数据类型建立
数据处理函数定义,假如我们定义的指令序列如下:
0x34 0x05 0x00 0x55 0x55 0x88
帧头0x34 0x05
指令0x00
数据0x55 0x55
校验和 0x88
那么我们就要建立这样一个函数来描述这种数据类型。
//udp接收回调函数变量申明
wiz_NetInfo remoteConfig[8]; //上位机网络信息
uint16_t remotePort[8]; //远端对象的端口号
uint8_t rxbuf[2048]; //接收缓存
void UDPCallback(uint8_t sn)
{getSn_DIPR(sn, remoteConfig[sn].ip);//获得远端IPremotePort[sn] = getSn_DPORT(sn);//获得远端端口号uint8_t len = getSn_RX_RSR(sn);if(len>0){recvfrom(sn, rxbuf, len, remoteConfig[sn].ip, &remotePort[sn]); printf("PC -gw:%d.%d.%d.%d\r\n", remoteConfig[sn].ip[0], remoteConfig[sn].ip[1], remoteConfig[sn].ip[2], remoteConfig[sn].ip[3]);printf("PC -port:%d\r\n", remotePort[sn]);if (sn == 0){printf("%s\r\n",rxbuf);sendto(sn,rxbuf,len, remote_ip, remotePort[sn]); }else if (sn == 1){/*---------------------------接收部分程序-----------------------------*///0x05 0x34 CC DD DD CHCommand recvcmd = *(Command *)rxbuf;if (len >= 6 && recvcmd.head[0] == 0x05 && recvcmd.head[1] == 0x34){uint8_t checksum = 0;for (int i = 0; i < sizeof(Command) - 1; i++)checksum += rxbuf[i];if (recvcmd.checksum == checksum)udp_broadcast(sn,(uint8_t *)rxbuf,len,8088); } }}
}