STM32CubeIDE LWIP UDP 网络通讯

        STM32CubeIDE 已经构建了一套良好的网络通讯框架,结合LWIP可实现大部分网络通信任务,现主要对UDP单播及组的配置进行说明:

        测试电路板采用STM32F4系列芯片+LAN8720方案构建的百兆网络,首先配置ETH外设,LAN8720芯片的PHYAD0引脚悬空,故PHY Address设置为0:

                            STM32CubeIDE LWIP UDP 网络通讯_第1张图片

        设置PHY为 user PHY:

                             STM32CubeIDE LWIP UDP 网络通讯_第2张图片

        其余配置保持默认即可,然后设置LWIP选项,首先勾选LWIP使能,最好同时使用FREERTOS,便于后面的数据处理。然后根据需求设置DHCP服务或者静态IP:

                              STM32CubeIDE LWIP UDP 网络通讯_第3张图片

         在初始化HAL_ETH_Init前,注意复位网络芯片,对于8720A,应拉低复位引脚再拉高。

         以上配置以实现网络功能,下载后网络连接即可正常,ping正常。要完成UDP通信,需要使能UDP服务:

                             STM32CubeIDE LWIP UDP 网络通讯_第4张图片

        组播通信需要使能组播相应设置:

                           STM32CubeIDE LWIP UDP 网络通讯_第5张图片

        注意,设置使能上面页面后,才能使能LWIP_IGMP。硬件配置完成,然后需要对生的代码进行修改,修改ethernetif.c文件中  static void low_level_init(struct netif *netif) 函数,增加NETIF_FLAG_IGMP:

              STM32CubeIDE LWIP UDP 网络通讯_第6张图片

       修改stm32f4xx_hal_eth.c文件中初始化函数 static void ETH_MACDMAConfig(ETH_HandleTypeDef *heth, uint32_t err) 

        设置 MulticastFramesFilter 类型为 ETH_MULTICASTFRAMESFILTER_NONE。

        完成以上设置后,即可开始编写用户程序,需要在程序初始化LWIP,调用MX_LWIP_Init();函数完成,一般在默认任务开始的时候调用,注意该函数可能需要较大的内存空间,需要把任务栈设置的更大一些。

        每一个UDP通道开启一个线程,线程中完成对UDP的端口绑定和回调。单播代码如下:

#include "lwip/udp.h"
#include "lwip/pbuf.h"
#include "lwip/igmp.h"

#include "lwipopts.h"

struct udp_pcb *g_upcb;

#define UDP_RX_BUFSIZE		1024	//定义udp最大接收数据长度
#define UDP_REMOTE_PORT		9000	//定义udp连接的端口
#define UDP_HOME_PORT		9001	//定义udp连接的端口

void l_User_NET_Task(void)
{
	err_t err;

	/* Create a new UDP control block  */
	g_upcb = udp_new();

	if (g_upcb)
	{
		/* Bind the upcb to the UDP_PORT port */
		/* Using IP_ADDR_ANY allow the upcb to be used by any local interface */
		err = udp_bind(g_upcb, IP_ADDR_ANY, UDP_HOME_PORT);

		if(err == ERR_OK)
		{
			/* Set a receive callback for the upcb */
			udp_recv(g_upcb, udp_receive_callback, NULL);
		}
		else
		{
			udp_remove(g_upcb);
		}
	}

	while(1)
	{
		osDelay(100);
	}
}

//UDP服务器回调函数
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
	unsigned int data_len = 0;
	struct pbuf *q;

	if(p!=NULL)	//接收到不为空的数据时
	{
		memset(udp_demo_recvbuf,0,UDP_RX_BUFSIZE);  //数据接收缓冲区清零
		for(q=p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
		{
			//判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于
			//的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
			if(q->len > (UDP_RX_BUFSIZE-data_len))
				memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_RX_BUFSIZE-data_len));//拷贝数据
			else
				memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
			data_len += q->len;
			if(data_len > UDP_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
		}
		upcb->remote_ip = *addr; 				//记录远程主机的IP地址
		upcb->remote_port = port;  			//记录远程主机的端口号
        
        //数据处理
        //.........

		pbuf_free(p);//释放内存
	}
	else
	{
		udp_disconnect(upcb);
	}
}

//UDP发送 外部调用
void L_udp_send(unsigned char *data, unsigned int length, unsigned int ipaddr, unsigned int port)
{
	struct pbuf *ptr;
	ip_addr_t send_ipaddr;  	//远端ip地址

	IP4_ADDR(&send_ipaddr, ipaddr&0xff,(ipaddr>>8)&0xff,(ipaddr>>16)&0xff,(ipaddr>>24)&0xff);

	ptr=pbuf_alloc(PBUF_TRANSPORT,length,PBUF_POOL); //申请内存
	if(ptr)
	{
		ptr->payload=(void*)data;
		udp_sendto(g_upcb, ptr, &send_ipaddr, port);

		pbuf_free(ptr);//释放内存
	}
}

        组播代码如下:

#define UDP_MULTICASE_RECV_PORT 1178    // multicast port for recive
#define UDP_MULTICASE_SEND_PORT 1180   // multicast port for send

#define  LWIP_DEMO_BUF     2048

struct udp_pcb* udp_server_multi_pcb;
ip_addr_t ipgroup_rev, ipgroup_send;

unsigned short lwip_demo_buf_len = 0;
unsigned char lwip_demo_buf[LWIP_DEMO_BUF];

void udp_multi_rev(void *arg, struct udp_pcb *pcb, struct pbuf *p,const ip_addr_t *addr, u16_t port);

void Multicast_Config()
{
	err_t err;

	IP4_ADDR(&ipgroup_rev, 226,0,0,80);//用于接收组播的地址
	IP4_ADDR(&ipgroup_send, 226,0,0,80);//用于发送组播的地址

#if LWIP_IGMP
    igmp_joingroup(IP_ADDR_ANY, &ipgroup_rev);   // 只需要将接收地址放入igmp组,发送的不需要
#endif

	udp_server_multi_pcb = udp_new();
    if ( udp_server_multi_pcb )
    {
        /* Bind the upcb to the UDP_PORT port */
        /* Using IP_ADDR_ANY allow the upcb to be used by any local interface */
        err = udp_bind(udp_server_multi_pcb,IP_ADDR_ANY,UDP_MULTICASE_RECV_PORT);

        if(err == ERR_OK)
        {
            /* Set a receive callback for the upcb */
            udp_recv(udp_server_multi_pcb,udp_multi_rev,NULL);
        }
        else
        {
            udp_remove(udp_server_multi_pcb);
            //PRINT("can not bind pcb");
        }
    }
    else
    {
     //   PRINT("can not create pcb");
    }
}

void multicast_send_data(unsigned char * data,unsigned short len)
{
    struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT,len, PBUF_RAM);

    memcpy(p->payload, data, len);
    udp_sendto(udp_server_multi_pcb, p,&ipgroup_send,UDP_MULTICASE_SEND_PORT);
    pbuf_free(p);
}

void l_User_Multicast_Task(void)
{
	Multicast_Config();

	while(1)
	{
		osDelay(100);
	}
}

// 组播接收,回调函数
void udp_multi_rev(void *arg, struct udp_pcb *upcb, struct pbuf *p,const ip_addr_t *addr, u16_t port)
{
	unsigned int data_len = 0;
	struct pbuf *q;

	if(p!=NULL)	//接收到不为空的数据时
	{
		memset(udp_demo_recvbuf,0,UDP_RX_BUFSIZE);  //数据接收缓冲区清零
		for(q=p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
		{
			//判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于
			//的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
			if(q->len > (UDP_RX_BUFSIZE-data_len))
				memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_RX_BUFSIZE-data_len));//拷贝数据
			else
				memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
			data_len += q->len;
			if(data_len > UDP_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
		}
		upcb->remote_ip = *addr; 				//记录远程主机的IP地址
		upcb->remote_port = port;  			//记录远程主机的端口号
//		lwipdev.remoteip[0]=upcb->remote_ip.addr&0xff; 		//IADDR4
//		lwipdev.remoteip[1]=(upcb->remote_ip.addr>>8)&0xff; //IADDR3
//		lwipdev.remoteip[2]=(upcb->remote_ip.addr>>16)&0xff;//IADDR2
//		lwipdev.remoteip[3]=(upcb->remote_ip.addr>>24)&0xff;//IADDR1

		udp_demo_recvbuf;
		data_len;

		LED1_Control_NON();

		pbuf_free(p);//释放内存
	}
	else
	{
		udp_disconnect(upcb);
	}
}

        UDP处理建议将数据发送到其他线程中,避免数据量大导致线程溢出问题。经过测试,组播和单播数据正常,长时间测试无卡死现象。

你可能感兴趣的:(c语言)